Compare commits
1125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1969669f51 | |||
| c639f342e5 | |||
| 6e887d0788 | |||
| 092863f113 | |||
| 5100a38fcd | |||
| 3bcecd8e08 | |||
| 486676d617 | |||
| 9e5280b4d1 | |||
| b70eed69d3 | |||
| d96b35694a | |||
| c7deeae11c | |||
| 323989953b | |||
| 17f9756e5c | |||
| 71f3931f2f | |||
| 1e9ca5f7fc | |||
| 8933c996cb | |||
| afa83cd5ae | |||
| 3e81ea7455 | |||
| 8102704726 | |||
| f4490069ca | |||
| 2ac4f92af3 | |||
| f98154a323 | |||
| 7b48460e9e | |||
| 3a0ef258d1 | |||
| 0d9a6796df | |||
| 08a59783f4 | |||
| e37afe996b | |||
| 6a387faa04 | |||
| 1a134cb4d2 | |||
| af4921adc5 | |||
| f94ee8ec97 | |||
| 356b024d6b | |||
| 2283bb01e6 | |||
| 3eef584c6b | |||
| 2e00a7de97 | |||
| e28348f6cd | |||
| 4b744d1fb0 | |||
| 2822998cad | |||
| 853815500d | |||
| 1a2925923f | |||
| cac884a85e | |||
| f314a6626a | |||
| bf83f4f343 | |||
| 1ef12e450a | |||
| 56fc6c3955 | |||
| 88cbf4ec42 | |||
| 26b3959968 | |||
| 966dd20b4b | |||
| 82998cac8b | |||
| 34b1f4161a | |||
| dfb297901f | |||
| 67236e9db3 | |||
| 8a00c3abf8 | |||
| 037f7f5921 | |||
| 8af3f28b67 | |||
| c5b2efd839 | |||
| a638e0b085 | |||
| f65e1ab936 | |||
| 70d14db881 | |||
| b142cf32ec | |||
| 87e26ef931 | |||
| 7366645c81 | |||
| 2437aba984 | |||
| a6627cd928 | |||
| aa8901268d | |||
| 9d306ad53a | |||
| 69b6c05970 | |||
| e55e18e4ce | |||
| f86db75b79 | |||
| 5728fcbf40 | |||
| 23f4f4a998 | |||
| ba2d91b489 | |||
| 40e2c374d0 | |||
| ce8feb7bfa | |||
| e823c4f93c | |||
| 497a342c27 | |||
| 32565765c1 | |||
| c1d07835bd | |||
| 65acef533a | |||
| bf2bb4c88c | |||
| bfc00c567c | |||
| 92d57db0d0 | |||
| 13331d0ffd | |||
| 359b1feb5a | |||
| a77ddba607 | |||
| caf23f078e | |||
| d5cb865aaa | |||
| 1c0aeb699f | |||
| c458c9e2b4 | |||
| 26a0e351f3 | |||
| 742f5b9af9 | |||
| 40e4546c48 | |||
| 5479117584 | |||
| 02453b02b0 | |||
| d282c55831 | |||
| 6977633de1 | |||
| bc0a72e2f9 | |||
| ef5c80abb3 | |||
| 35a33fc617 | |||
| 494840c908 | |||
| b7d9fdd5bd | |||
| 32299a6808 | |||
| d7cfb729c3 | |||
| 348b526212 | |||
| 18bfa1d6b3 | |||
| 5d2dfa9f40 | |||
| 0b5d24148b | |||
| dfb2594793 | |||
| b5b6beacb0 | |||
| 213563583d | |||
| 9c7638a015 | |||
| fef7e0521c | |||
| 335cb1bdf3 | |||
| 0b2d8461fd | |||
| 9376ccbedd | |||
| 03b1cbb347 | |||
| 51f50e8b8f | |||
| 2e9a79a375 | |||
| 86a26e0baf | |||
| 6f2981134d | |||
| e58cc37c49 | |||
| baebce8b05 | |||
| 2bfaec75e0 | |||
| 36c2e7cd79 | |||
| 3284b04f2b | |||
| 8c662d63bf | |||
| 3cb387ebd0 | |||
| e90a4a74cb | |||
| d338b4425b | |||
| c128712d4d | |||
| 8b2881b0c2 | |||
| 3a9081b5b1 | |||
| 54bd4e4d3a | |||
| cc1cd9619f | |||
| 007261d19e | |||
| dc22b0a877 | |||
| 4178333174 | |||
| f012dac2cb | |||
| 8def6752f3 | |||
| 0f9c1b33c7 | |||
| f530c99415 | |||
| 5470415bfc | |||
| 7e5324c80e | |||
| e893fc9da4 | |||
| b2e1907c3a | |||
| 21086cd79e | |||
| ee15ce2243 | |||
| 1eb6f65e40 | |||
| 238ffe64a7 | |||
| cea176fa9f | |||
| 3276e3fef5 | |||
| e40d9b8c0d | |||
| 30d08cfbb2 | |||
| 29d902918d | |||
| f89a5bcf70 | |||
| 8ac077a721 | |||
| 666635c969 | |||
| 4c1cfcf6f3 | |||
| 76c8afde8a | |||
| 5714495f8c | |||
| d8371615ce | |||
| 7630861fde | |||
| da594dcecd | |||
| 4fe5a19221 | |||
| 330a466503 | |||
| c0b8eddadb | |||
| 76b54e3e44 | |||
| 36489c7470 | |||
| dd8adda3ff | |||
| fb1f0dca2a | |||
| 70fcc45893 | |||
| 511d71a110 | |||
| 57abd9baef | |||
| e6b0430a87 | |||
| 6f286033c5 | |||
| 1600b10131 | |||
| 475bb4d911 | |||
| c5517f7872 | |||
| 40353c9150 | |||
| c7e23ccedf | |||
| 361beab0b0 | |||
| 07fbc894e7 | |||
| 098b39d07a | |||
| 60e537f9bc | |||
| 5cfc9f84b8 | |||
| 6ddce0ac92 | |||
| e87ee10f8b | |||
| e171aba8b5 | |||
| 1bd0e927d2 | |||
| e113f8ed12 | |||
| 336958352a | |||
| 5dc15aeaaf | |||
| 9a22e2b737 | |||
| 76d3252cd0 | |||
| 9756c9a392 | |||
| 0fac568a19 | |||
| 04897513cd | |||
| 5365831de8 | |||
| 6a69c3c743 | |||
| c0793cb58b | |||
| 327af97ccc | |||
| b78b99b695 | |||
| a1bff4b035 | |||
| 0b673d7cb6 | |||
| db134f36ae | |||
| f873068a2c | |||
| 38a4000a97 | |||
| 0f5e05c088 | |||
| 65000d3245 | |||
| 312ed1a4a5 | |||
| ce5738b4a6 | |||
| 447db4c77c | |||
| b47aa5dadc | |||
| af0215fb9f | |||
| 2791333c70 | |||
| 96ea5367db | |||
| 0298e9be7d | |||
| 7c6d645a0a | |||
| 0c2fbaf187 | |||
| d254d7496c | |||
| 2c5d86f197 | |||
| 8df24304d8 | |||
| 93b123761b | |||
| cdbbb5b7a6 | |||
| 86af067e42 | |||
| db3bffa971 | |||
| 2cb17590c2 | |||
| f7601da006 | |||
| 65a0e5f3b6 | |||
| d5d66f1276 | |||
| 9ed14fa0f4 | |||
| f24ebc438e | |||
| 90d50dcbc4 | |||
| ce2458c1df | |||
| 8871893993 | |||
| 5f68be2273 | |||
| 68ae67dc58 | |||
| c4a85ab1d1 | |||
| ef3c885630 | |||
| bb35d04102 | |||
| ba05fd363b | |||
| e988b0f660 | |||
| b27cce08af | |||
| a38896c254 | |||
| 63ae13fcf9 | |||
| c5f6d0ff17 | |||
| ab8c4e9198 | |||
| 8daa4193c7 | |||
| 5c895eaad8 | |||
| 435d0d68b3 | |||
| 66e9845f05 | |||
| fe3340077c | |||
| c589660e17 | |||
| d9117c77e1 | |||
| 6cb68f26c9 | |||
| c60764c34f | |||
| ba723826b7 | |||
| 0165d7bdff | |||
| 1df1f9a1c5 | |||
| 2c916acc06 | |||
| 0a810b226c | |||
| 57c8f5934f | |||
| 125e669723 | |||
| a2486a6d66 | |||
| 15f2898f26 | |||
| 1c4adde1b6 | |||
| 4d22991245 | |||
| 90ea6c6bd1 | |||
| 269cecd3ee | |||
| c2be20f09e | |||
| df56fa2716 | |||
| f49e2539b7 | |||
| 4da7b3b7f0 | |||
| e5a5cd0070 | |||
| c5779deeb6 | |||
| 79c944aef4 | |||
| a23c6a2871 | |||
| 7acc693277 | |||
| 889698f7bd | |||
| f6f6d9282b | |||
| 5224884e5e | |||
| f063b023bd | |||
| a1edb167b1 | |||
| 613fac4bab | |||
| e0cd11ab21 | |||
| 5b818ad75c | |||
| 17ba6a7c4d | |||
| f77a8b9e17 | |||
| 3dd8c2cda6 | |||
| 48a8ed857e | |||
| 74c3511d5c | |||
| 5ac113cfaa | |||
| cd02954c91 | |||
| 93be1d0401 | |||
| 6a38811f1a | |||
| 4ac45a3967 | |||
| d7e01bb821 | |||
| 340bf3cb7a | |||
| 5f8547a5e0 | |||
| 09e9c0550a | |||
| c1dc80571a | |||
| ec9b23ba9c | |||
| 7c0b56f9ba | |||
| d90a45514f | |||
| b2fe0778dc | |||
| 3e4f254573 | |||
| 2e4dd13482 | |||
| 19331ca287 | |||
| 9483f6378f | |||
| a4e933688a | |||
| c11e9f3316 | |||
| acdcf9b6ae | |||
| c702840a0a | |||
| 254fae2dc9 | |||
| bdad3e8c8e | |||
| 574d634fc2 | |||
| eafbd2a597 | |||
| 7485b035af | |||
| 8014a7023c | |||
| bec34e7227 | |||
| 9ca6322603 | |||
| 957fe1db93 | |||
| 3b4738c2ca | |||
| 30ebb852a3 | |||
| 959586bc8f | |||
| 9bda98486d | |||
| c68e4e76f4 | |||
| 98a8231cef | |||
| 28ce13d18e | |||
| ef59cd88dc | |||
| 9db6cdc261 | |||
| f7b29333f5 | |||
| 24520fc982 | |||
| b93cc989f4 | |||
| a8cc243aa7 | |||
| e65dc4c6d6 | |||
| d4be92713a | |||
| 79f5d39db5 | |||
| 6da3fcdef1 | |||
| 026130bd5f | |||
| c1b48fb5c8 | |||
| b1983548aa | |||
| c62812a944 | |||
| d66b96260a | |||
| d7e9c3bbfd | |||
| 7a4a865bdb | |||
| da4dcf3753 | |||
| 5383b0e603 | |||
| 9d2e6404b3 | |||
| 91a73bd191 | |||
| c069542c5c | |||
| 127f35a9bc | |||
| 171e815122 | |||
| 3ac5cafe53 | |||
| a7ad739036 | |||
| a84bec9709 | |||
| 05dbf82edb | |||
| c75eb47158 | |||
| 9945864a32 | |||
| beeea4b292 | |||
| 7a3159c9af | |||
| ccd6b5cd7d | |||
| 908e85d9f9 | |||
| e341526f46 | |||
| 03461ce908 | |||
| 8c37975d44 | |||
| 4d55ef1132 | |||
| 342d47cfb5 | |||
| 554abdd57a | |||
| 061598f011 | |||
| d754a7ceea | |||
| 643c9f912f | |||
| 4965421215 | |||
| 18aab5b786 | |||
| b905dddf19 | |||
| 11a9c46904 | |||
| b1ec912db5 | |||
| db45b7c644 | |||
| 2256a53830 | |||
| 03ef55672d | |||
| c45bfab318 | |||
| b2fcab827d | |||
| 1471da95a9 | |||
| 5a530af189 | |||
| 3c9f5ad56e | |||
| c5d9e0e03c | |||
| 0094997aa1 | |||
| 7a411b6bb5 | |||
| c2341cd47a | |||
| 4219b22978 | |||
| 08ab7e8de7 | |||
| 3ef6bcface | |||
| ab97dfa19c | |||
| af1ccf93a5 | |||
| 015cd9e776 | |||
| 3d44bcdcc4 | |||
| 1817cbc3a4 | |||
| e0bf8ece3c | |||
| 37bdf14b49 | |||
| 292e69f89a | |||
| dd876e69c8 | |||
| 4e644d7465 | |||
| 596fd9d0c6 | |||
| 474f68600e | |||
| 50774e407c | |||
| 8e63e2c540 | |||
| 273c4e9403 | |||
| c273f49077 | |||
| 9a8bdfd765 | |||
| 23dfbe4ae2 | |||
| b57e5d93b8 | |||
| f0f9907b9d | |||
| c4fc630413 | |||
| aabbf29542 | |||
| b7898befef | |||
| 6721f0655e | |||
| d37d6ecd99 | |||
| e4db698184 | |||
| e661a4b1c3 | |||
| f5164f86b7 | |||
| 9e0c1549f4 | |||
| 55b455e9ae | |||
| 34933ef741 | |||
| bbf94cdb63 | |||
| 593fc365d0 | |||
| 1edf3515e3 | |||
| 0d271f9477 | |||
| 3534f77761 | |||
| 64bbaeccc4 | |||
| de002b0d9a | |||
| 307302ed32 | |||
| a60f77ad36 | |||
| 383c3fb3e4 | |||
| a64f8c28bd | |||
| 377d1b1cd8 | |||
| dde2263fe7 | |||
| 434619053a | |||
| b739cd11c8 | |||
| bc6ef0dad8 | |||
| 3ac0dd7093 | |||
| 0f0fe82350 | |||
| 6be4bfd026 | |||
| d9d209f34d | |||
| 2059786f7b | |||
| 6008f987d3 | |||
| 635e8f11f3 | |||
| 9b5fa1d7ed | |||
| 1955e90493 | |||
| 222e87daeb | |||
| a89ccb789e | |||
| 2013b7ae81 | |||
| 3e1100b71a | |||
| 086afe852b | |||
| abcdf18547 | |||
| 4a304688f4 | |||
| d3377bf911 | |||
| 481cbea10c | |||
| b817cbc0ed | |||
| 15275048b6 | |||
| 3baccfee3a | |||
| bb76c21275 | |||
| c92c4efb66 | |||
| 39203484a6 | |||
| 2a2d605074 | |||
| 0676346aeb | |||
| 93fdb69ee3 | |||
| c7921fe402 | |||
| 3660e6d02f | |||
| 083322a217 | |||
| 8225105cf2 | |||
| ccd3276dcf | |||
| f51eddef6d | |||
| 4f8aa1d8a0 | |||
| 060d46a282 | |||
| 820c3be7df | |||
| a562ca1be3 | |||
| 658761d953 | |||
| 1b69ea13f5 | |||
| 6e5ac34bc2 | |||
| ec9f02c7ab | |||
| f75bee62b5 | |||
| e77123cf0e | |||
| 8dea57d271 | |||
| 634761b8a7 | |||
| 01aac350fa | |||
| 08d611f49b | |||
| f6de358b06 | |||
| f66904ab90 | |||
| 6a363f65e3 | |||
| 736db20c9f | |||
| ba8ea8f0cc | |||
| eab4ea3fb9 | |||
| 06d9e4bba7 | |||
| 38ce329e7e | |||
| 94f088d7e8 | |||
| 89fb081983 | |||
| aae0affae4 | |||
| 8190e81ba8 | |||
| 16a2a6dc55 | |||
| b7999978f9 | |||
| e1711f357a | |||
| 501eb4f916 | |||
| f341159300 | |||
| cac98f5fd5 | |||
| 5eca0802d2 | |||
| 6159e923e6 | |||
| 2ceb57915a | |||
| 61c144b9f6 | |||
| 8537e8d9fa | |||
| 33d42e25e6 | |||
| 7c5d2771f7 | |||
| 098bede0ec | |||
| 629e58ca5b | |||
| e3ab3fc239 | |||
| f1123e71ae | |||
| 778267aa75 | |||
| d6da13d4e3 | |||
| 92a4f2ddbf | |||
| ded25a9ef8 | |||
| 1bae1aab03 | |||
| 01a1e8e400 | |||
| 9487cb508d | |||
| 461751e5cd | |||
| 762479c15b | |||
| c8607467bd | |||
| f8e98d6a7f | |||
| 4aa0b82381 | |||
| f9522b30dc | |||
| ec26c7492d | |||
| 684092bae1 | |||
| 9cbf4688b2 | |||
| 74857ba474 | |||
| f1b61c629a | |||
| a565bdd57d | |||
| cf5bbd597a | |||
| c0d8fc0d37 | |||
| eef43402b6 | |||
| 54bd9e6112 | |||
| fa7edec7c8 | |||
| 89cc29b4c2 | |||
| 686a9993f5 | |||
| e16c5fe905 | |||
| dedb06b2f5 | |||
| 5fc3f57ede | |||
| 5789dae8be | |||
| 0efde9c4ed | |||
| 7eeaed992c | |||
| f0ce6f501d | |||
| 823d1b9990 | |||
| 61760eabc5 | |||
| 12095a12f4 | |||
| 412726d39c | |||
| 136c959191 | |||
| 2ac4cd43da | |||
| f603052b35 | |||
| 47be38e63c | |||
| 9ca775cba3 | |||
| e4d49e6df7 | |||
| 02bf34aa96 | |||
| 73ef3e9a39 | |||
| 55ef99b667 | |||
| 57686d3149 | |||
| 8396960ec0 | |||
| 3e8df323b3 | |||
| 706229da77 | |||
| 2c10a8e687 | |||
| e4c4891a47 | |||
| 80d647a9b0 | |||
| c5e8f09b10 | |||
| 08643fe679 | |||
| 3df3610199 | |||
| 00cbb348a7 | |||
| cde6c3aaf4 | |||
| c265deedef | |||
| 0333d3dbf4 | |||
| 58324bdc50 | |||
| 04c1c098ed | |||
| f2458d1dcf | |||
| ce781a1832 | |||
| b85943dc0a | |||
| d4ca1db60b | |||
| 47d567e53c | |||
| 6b8d3538ef | |||
| 05f3fbae73 | |||
| 65e33fed9b | |||
| d334c1ccfe | |||
| fe8358add2 | |||
| 2c76ab718b | |||
| aa4dfda44c | |||
| 66ae58de17 | |||
| ce1673803f | |||
| 73adba8b81 | |||
| c11d577349 | |||
| be1ed10363 | |||
| 8a987b9c55 | |||
| c75ff7ecd9 | |||
| 92075113d8 | |||
| 13bdd914dc | |||
| 60b0c7a914 | |||
| 212399feaf | |||
| 08d83d136d | |||
| dd6424fee8 | |||
| 720a846cf8 | |||
| bfdbb7089a | |||
| 16a384ab4b | |||
| a3e1a9a44d | |||
| 6a69c2f699 | |||
| db888fa5d4 | |||
| c9b844a721 | |||
| 604ad455ee | |||
| 8d493b686e | |||
| c0e7e34916 | |||
| 1a53141502 | |||
| 1285fb22dc | |||
| 4550fff1af | |||
| 7c4e5b14cb | |||
| f72d3dfa32 | |||
| e67539752e | |||
| db0f36f431 | |||
| 7e9d3f2b50 | |||
| 4015e0666e | |||
| cee62eb28e | |||
| 735080a84b | |||
| 161e7997fa | |||
| 417d79780f | |||
| 41a54f05cb | |||
| 84c1c24ce2 | |||
| af4c9719b3 | |||
| 39a3b73f85 | |||
| e8623fdc91 | |||
| 06dbd774a1 | |||
| a280501ff1 | |||
| 891a9e8bdf | |||
| 7ae39fffc2 | |||
| a56d9a6661 | |||
| b0048c0aca | |||
| 0983ed29bb | |||
| 88763cfbe1 | |||
| 19dd7f70c1 | |||
| 42cdda38bb | |||
| f0fd84770a | |||
| a2d0ea78be | |||
| e9d034bd31 | |||
| a3d51b77e9 | |||
| 4b65110e4d | |||
| eba13f0ca8 | |||
| c1934706bb | |||
| f67a7b64c8 | |||
| 628e84068e | |||
| 78a32e93fd | |||
| 859bfea502 | |||
| 2973798ca1 | |||
| b9a15f071c | |||
| f11dbe81d2 | |||
| 6bc9d68485 | |||
| 5cd6b553a5 | |||
| 5b13dc62ce | |||
| 4d381f0b58 | |||
| 84ce84f501 | |||
| 5b0897701b | |||
| 5465246245 | |||
| e183e69997 | |||
| 779ea5f0ad | |||
| 00d1963484 | |||
| 5ac3cde2bd | |||
| 2a08dec648 | |||
| b7268856d7 | |||
| 1b2c7a3193 | |||
| b5962e8ee9 | |||
| ece509d579 | |||
| 2935000485 | |||
| 708b184397 | |||
| 9a11544d87 | |||
| 2451d757bd | |||
| ea97dea902 | |||
| a19cc91537 | |||
| 796b281bf1 | |||
| 72fd120dc8 | |||
| 4794871a9a | |||
| 13d25b4109 | |||
| 21b50c962e | |||
| 54b29af818 | |||
| 8f34fa4c88 | |||
| 872e336462 | |||
| b7a112cbec | |||
| 5a9c74be03 | |||
| 38addf2e24 | |||
| 3878c000fe | |||
| 1324ae6262 | |||
| 3a52ad2f53 | |||
| c23c992f14 | |||
| 73f477be9d | |||
| 4797b42005 | |||
| 6845fd8339 | |||
| 8e2443ada4 | |||
| 84ea8d7f90 | |||
| 1aed2b8abd | |||
| 191df1cb73 | |||
| b6be1e2810 | |||
| a1acfa89c2 | |||
| 93ddd92662 | |||
| 3131c90344 | |||
| 3b484491ce | |||
| 3b24bd7abd | |||
| e9013d761f | |||
| c49b216c1f | |||
| 16ddf6560a | |||
| 0e733c7754 | |||
| 9c3ecfe338 | |||
| 771850b9cf | |||
| 355696f3dd | |||
| 342b743b13 | |||
| 40a4a25dd8 | |||
| 25979e715e | |||
| 82e0e9f38f | |||
| 549f1ebc8f | |||
| 00803bcd7f | |||
| af2f67d98c | |||
| 752c2d6e7c | |||
| 3c3344e1f5 | |||
| 68e2e45f7d | |||
| 6a81b6dfb3 | |||
| 4e76439c79 | |||
| 0d39f11034 | |||
| 893cf61c21 | |||
| ebc1eae679 | |||
| 29e5bee34e | |||
| de41192ea7 | |||
| b3055067ac | |||
| 9109233886 | |||
| e49c9d2600 | |||
| 87b6021428 | |||
| 50f5080912 | |||
| 3641723193 | |||
| 8b215af818 | |||
| 586a9bb631 | |||
| 00c3238e50 | |||
| e07775c7c3 | |||
| 4579b303e6 | |||
| 32d3c2bc79 | |||
| ba43451bd6 | |||
| f1e42ba5fa | |||
| 163a66c64c | |||
| 876e66e555 | |||
| 93cd5583eb | |||
| 5797f5852e | |||
| 15d2d76972 | |||
| 5cb4a5424d | |||
| 4d1a9cdc1d | |||
| c3bf407adf | |||
| 14e14f1b87 | |||
| bffe045267 | |||
| 92a28881b1 | |||
| ebac6784d4 | |||
| d9376439b1 | |||
| d718bb72e9 | |||
| 40ea9172f9 | |||
| 787f8234ce | |||
| cc20908bb3 | |||
| a7eb557680 | |||
| 0fe73a6780 | |||
| a905be2260 | |||
| 8f5f38c6a5 | |||
| d82900d3a8 | |||
| e62715870a | |||
| 2e19ceb0aa | |||
| a0bc100289 | |||
| 5e60ee266b | |||
| 7c29c2ebf2 | |||
| 4b7b23cf20 | |||
| 2dee3413cf | |||
| 59cbec3fc9 | |||
| 3e2eb71316 | |||
| 06115a0456 | |||
| 9cf9b7b223 | |||
| 1fda4865c8 | |||
| f0e55e1096 | |||
| 74c71bc8a4 | |||
| 0b5609f587 | |||
| 2fc5e46f1b | |||
| ef6774325a | |||
| 7450cfc627 | |||
| 29988c8b1d | |||
| 34bb64eddd | |||
| 873049c346 | |||
| a722a3a110 | |||
| f2942a7f92 | |||
| ae367d3645 | |||
| 6022e96b4d | |||
| 2286ef4b36 | |||
| 0dd96ebc57 | |||
| 06b99c8ef2 | |||
| b357a212dd | |||
| 2e704f996b | |||
| 284547080c | |||
| 07e3da08d0 | |||
| dfd79bde56 | |||
| 317d6482e4 | |||
| 5f51abd142 | |||
| b9288efc4a | |||
| c90fb34d6d | |||
| 75abd94bf5 | |||
| 55e48112f8 | |||
| 4b65570ba5 | |||
| 9aefda7c1a | |||
| bb485009c3 | |||
| 24fe68d925 | |||
| 3780d9cda8 | |||
| e10703cd7b | |||
| 824afb84c9 | |||
| aee33863c8 | |||
| 90c29d8f23 | |||
| e686911210 | |||
| 8bb830824b | |||
| a6a0cb4331 | |||
| 2200d8d173 | |||
| c34a000e78 | |||
| fb51cdaca3 | |||
| 50423936b2 | |||
| 49994c8915 | |||
| 17dbc7e055 | |||
| 312c386cda | |||
| 96952b92f8 | |||
| 3e2333e5ed | |||
| fc7c919b43 | |||
| c7e86f9374 | |||
| 5da9376222 | |||
| 5461565945 | |||
| bee06cf264 | |||
| cd238fe567 | |||
| 09b06509a8 | |||
| e771239608 | |||
| e0af160213 | |||
| e5380d646c | |||
| 621c3eebf3 | |||
| 4a94f76ec3 | |||
| c0ddd5c5d8 | |||
| a6e55e3528 | |||
| ffeb16eb66 | |||
| a802d8b19a | |||
| 555d10dea7 | |||
| 3f2355a36a | |||
| bceb298edb | |||
| 47b9466fad | |||
| 8f80f875b5 | |||
| 2941bbae2d | |||
| 8842e450db | |||
| f5737aa3cc | |||
| b8fd3d2d8e | |||
| 387250cd83 | |||
| 433d83221a | |||
| 3151804cf8 | |||
| f66e9af515 | |||
| f92abd2ba5 | |||
| 700c5bf5b5 | |||
| 87c37bb182 | |||
| 9cc9a491a8 | |||
| 76a8a1637f | |||
| 568f6424e5 | |||
| f32a8f7ea0 | |||
| e0b50c8a70 | |||
| 0294f5ec55 | |||
| 53987e31aa | |||
| f59858543c | |||
| cae7be1a60 | |||
| 4ccfe242e8 | |||
| 811963ad26 | |||
| 41d932017b | |||
| 7a24407ecc | |||
| df68c211b5 | |||
| 2ca1ea92ff | |||
| 8f546d89b4 | |||
| 6d3cb5bcd3 | |||
| ca5b0c56f0 | |||
| 0d58b93a84 | |||
| b7548e68a8 | |||
| 2f3e8c2bd3 | |||
| fc2a8fd59a | |||
| 47b24beb6c | |||
| 895ff6f452 | |||
| fce7f5c56b | |||
| b5e8dd5edc | |||
| d4fabc0464 | |||
| 971d6aafd4 | |||
| 0457824193 | |||
| 40ad3a1bf9 | |||
| de2680152b | |||
| 541ba67daf | |||
| 6576d894a3 | |||
| 8e1dade106 | |||
| 2ebdd028cf | |||
| b5a48f9f6c | |||
| d70ca981c9 | |||
| 4b719d7443 | |||
| 5b9f6b2192 | |||
| 4e242e9211 | |||
| 94f3c76039 | |||
| 67a7c5601a | |||
| 4ea0c0936d | |||
| 7bd9a79b12 | |||
| bfb560045c | |||
| c5da46f125 | |||
| 10fdd01dc8 | |||
| 1e9e781c12 | |||
| 24b38126ec | |||
| 3577a6989d | |||
| 48c49c54e2 | |||
| d4993c0ba4 | |||
| 213b7c57ee | |||
| b9b1f2e7f0 | |||
| cdd5210bb7 | |||
| bc90a79581 | |||
| e6e1260bc2 | |||
| 1d555e724b | |||
| e6fe480a30 | |||
| 91a06063b4 | |||
| 49fc496c12 | |||
| 94abeeabb9 | |||
| b7e506341f | |||
| fba3fd81fa | |||
| e875c0de26 | |||
| 43f058df6d | |||
| 01c0ef0013 | |||
| a46fae0687 | |||
| 5a646a0d20 | |||
| 9a73dcad53 | |||
| b605cf74d2 | |||
| 220376b6e2 | |||
| cfb6bf95a3 | |||
| 3905843257 | |||
| 501a164eba | |||
| 9e725cb28a | |||
| e02cdc0c54 | |||
| 0038720c01 | |||
| d4279559fa | |||
| 1ea9d7d4de | |||
| f7abd084c6 | |||
| d82e0ef5c2 | |||
| 3027856c7b | |||
| 9de7bd666d | |||
| 92b51563b5 | |||
| 389c7aa5af | |||
| bda54a9aed | |||
| 09b1239131 | |||
| 662f2447e3 | |||
| 20005f5bbc | |||
| 803a1a5489 | |||
| 20ba29590a | |||
| 13554a3d83 | |||
| 9bfc7c7768 | |||
| 045ef7b801 | |||
| e69355f78f | |||
| 9d14c841d7 | |||
| f796f01f38 | |||
| 350299786d | |||
| 445c0959b5 | |||
| 0c5aae157a | |||
| 2e7d0d4934 | |||
| e9a835ac55 | |||
| b55806017a | |||
| b79ce2a549 | |||
| a155795837 | |||
| e935da461a | |||
| 92bf2cf105 | |||
| 0840af5277 | |||
| 967614f64a | |||
| 891d807137 | |||
| ecddbcf240 | |||
| aacf7c4613 | |||
| 3a35480c7a | |||
| 7c962d2f61 | |||
| dc882d6905 | |||
| 15146d900c | |||
| ad5a0e42d0 | |||
| a56e29704e | |||
| f344290368 | |||
| 5ae8c936e7 | |||
| 5413d2e5a3 | |||
| 226a1dc391 | |||
| ec810dd6db | |||
| 5493212578 | |||
| 45d365f98b | |||
| 94324e3f46 | |||
| d2762938b7 | |||
| 765d126ee9 | |||
| 896c4cbccc | |||
| 325074981c | |||
| b26f38a6aa | |||
| d13505b6c6 | |||
| 67f9d8f655 | |||
| c353e60913 | |||
| 662fb712bc | |||
| 28165f7b4e | |||
| dbcfda77d9 | |||
| 1dfa7776e1 | |||
| 40a9c15345 | |||
| 3dc3e066aa | |||
| bbf808c347 | |||
| 4556e06fcf | |||
| f605a4e430 | |||
| fa1758b950 | |||
| 14903d6a05 | |||
| 5b08af2e8d | |||
| 6cb05c3762 | |||
| 94954177e5 | |||
| 3d89df13ef | |||
| 15f2d240d6 | |||
| 85dfac5c06 | |||
| 860bcc02c6 | |||
| 4daeeff0e3 | |||
| 32de4c9e7c | |||
| f0c123d4c0 | |||
| 8c22fe84c2 | |||
| 9ff453a50b | |||
| 4e6b03d668 | |||
| b635fd9edd | |||
| a387952493 | |||
| 867a6d6731 | |||
| b20c9470c7 | |||
| 7523c420bd | |||
| 98cb33599f | |||
| 9281719d74 | |||
| 8e04a9f844 | |||
| 6df4371df6 | |||
| be13072f0c | |||
| 989df7454c | |||
| 7e87dbaa39 | |||
| 1e5cdfd805 | |||
| bb0006b683 | |||
| 786117dd49 | |||
| 43ba774e52 | |||
| f7439671bc | |||
| 48df51aa3b | |||
| 4a77702473 | |||
| 15c1a0e5e7 | |||
| 9c4b734361 | |||
| 5b48a7d92c | |||
| 4316be95c2 | |||
| 19bc1df15d | |||
| bda2317d8b | |||
| 1f07530a54 | |||
| d5f5778492 | |||
| 1ecc63dfec | |||
| 1e4e8b70d9 | |||
| e45cc16a90 | |||
| c26b4525aa | |||
| ea29d2b2cd | |||
| 5559a93d8a | |||
| 921123155a | |||
| 2a1f2d59ee | |||
| 3e70b01ec5 | |||
| b55527739a | |||
| bf97d21c03 | |||
| c554987912 | |||
| 6f09dedfad | |||
| 0a54360211 | |||
| 5c24263779 | |||
| 5b609ef9e2 | |||
| 36bf8ac911 | |||
| efe3877e3c | |||
| 433ca2ecb6 | |||
| fec98e2798 | |||
| c188bee3e3 | |||
| 0e2cc609df | |||
| adf391fdec | |||
| 5d529edfb1 | |||
| 59eeeadbac | |||
| 7265e2e88c | |||
| 5c019d2aef | |||
| 679a3f1915 | |||
| 60abb34497 | |||
| 94ff77f2f2 | |||
| e2eb95a29f | |||
| 10be691b86 | |||
| fcdb6d8a54 | |||
| 32502bbd1a | |||
| 71ffb0028e | |||
| 803c0a6e22 | |||
| 8ee4aa6de4 | |||
| 91261b2757 | |||
| 812c91cc9e | |||
| 27da832d6d | |||
| aa554d985d | |||
| 804c4bbfa6 | |||
| 1a658054ab | |||
| 33e39a2471 | |||
| 538a856c2f | |||
| 877e7efb5f | |||
| bdd43beaf3 | |||
| 307d60a0c8 | |||
| 12758046e1 | |||
| 9aaf87c19b | |||
| 400bbbac11 | |||
| 226eae97cb | |||
| e5d7b3801d | |||
| 4e1630e126 | |||
| 40b13c28b8 | |||
| 2de621cda0 | |||
| 5c5a1eae8a | |||
| 8f113b48cc | |||
| 949e0b3aff | |||
| 448ac9dcd4 | |||
| dd21df3ce4 | |||
| 3484389dcd | |||
| ace0977930 | |||
| 3df07feb11 | |||
| 153d598df3 | |||
| a22e2618a6 | |||
| 3582facd34 | |||
| 2a079911b5 | |||
| 5fae96eb6e | |||
| e3d07bc1a0 | |||
| f53866d0c0 | |||
| e5754ae96f | |||
| 294bbad407 | |||
| 83e43a42d9 | |||
| 2485d57f66 | |||
| 65c35b6595 | |||
| 1ea5c91fcc | |||
| 76c5b9d021 | |||
| 229f112ff3 | |||
| fae4365983 | |||
| e7f45ca90e | |||
| 3236d33b6a | |||
| 898efb69a3 |
@@ -0,0 +1,19 @@
|
||||
Mininet uses GitHub issues for bug reports and feature requests only.
|
||||
These issues can be viewed at bugs.mininet.org
|
||||
|
||||
If you have a question that is not a bug report or a feature request,
|
||||
please use the documentation at docs.mininet.org, the FAQ
|
||||
at faq.mininet.org, and the mininet-discuss mailing list.
|
||||
|
||||
For bug reports, please fill in the following information in detail,
|
||||
and also feel free to include additional information such as debug
|
||||
output from mn -v debug, etc.
|
||||
--- Cut Here ---
|
||||
### Expected/Desired Behavior:
|
||||
|
||||
### Actual Behavior:
|
||||
|
||||
### Detailed Steps to Reproduce the Behavior:
|
||||
|
||||
### Additional Information:
|
||||
|
||||
@@ -41,16 +41,21 @@ load-plugins=
|
||||
# can either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once).
|
||||
disable=W0704,C0103,W0231,E1102,W0511,W0142,R0902,R0903,R0904,R0913,R0914,R0801,I0011
|
||||
disable=pointless-except, invalid-name, super-init-not-called, fixme, star-args,
|
||||
too-many-instance-attributes, too-few-public-methods, too-many-arguments,
|
||||
too-many-locals, too-many-public-methods, duplicate-code, bad-whitespace,
|
||||
locally-disabled, locally-enabled
|
||||
|
||||
# bad-continuation, wrong-import-order
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html
|
||||
output-format=colorized
|
||||
msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'
|
||||
|
||||
# Include message's id in outpu
|
||||
# Include message's id in output
|
||||
include-ids=yes
|
||||
|
||||
# Put messages in a separate file for each module / package specified on the
|
||||
@@ -58,7 +63,7 @@ include-ids=yes
|
||||
# written in a file name "pylint_global.[txt|html]".
|
||||
files-output=no
|
||||
|
||||
# Tells wether to display a full report or only the messages
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=no
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highes
|
||||
@@ -108,10 +113,10 @@ const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Regular expression which should only match correct function names
|
||||
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
function-rgx=[a-z_][a-z0-9]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct method names
|
||||
method-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
method-rgx=[a-z_][a-z0-9]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct instance attribute names
|
||||
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
@@ -120,7 +125,7 @@ attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
argument-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct variable names
|
||||
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
variable-rgx=[a-z_][a-z0-9]{2,30}$
|
||||
|
||||
# Regular expression which should only match correct list comprehension /
|
||||
# generator expression variable names
|
||||
@@ -191,7 +196,7 @@ additional-builtins=
|
||||
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
defining-attr-methods=__init__,__new__,setUp,build
|
||||
|
||||
|
||||
# checks for sign of poor/misdesign:
|
||||
@@ -264,7 +269,8 @@ int-import-graph=
|
||||
max-line-length=80
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1500
|
||||
# XXX 1500 -> 4000 for miniedit.py
|
||||
max-module-lines=4000
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
language: python
|
||||
sudo: required
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- dist: trusty
|
||||
python: 2.7
|
||||
env: dist="14.04 LTS trusty"
|
||||
- dist: trusty
|
||||
python: 3.6
|
||||
env: dist="14.04 LTS trusty"
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq vlan
|
||||
- PYTHON=`which python` util/install.sh -n
|
||||
|
||||
install:
|
||||
- bash -c "if [ `lsb_release -rs` == '14.04' ]; then make codecheck; fi"
|
||||
- pip install pexpect || pip3 install pexpect
|
||||
- util/install.sh -nfvw
|
||||
|
||||
script:
|
||||
- alias sudo="sudo env PATH=$PATH"
|
||||
- export PYTHON=`which python`
|
||||
- echo 'px import sys; print(sys.version_info)' | sudo $PYTHON bin/mn -v output
|
||||
- sudo $PYTHON bin/mn --test pingall
|
||||
- sudo $PYTHON mininet/test/runner.py -v -quick
|
||||
- sudo $PYTHON examples/test/runner.py -v -quick
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
|
||||
# More details: https://docs.travis-ci.com/user/notifications
|
||||
@@ -0,0 +1,52 @@
|
||||
Mininet Contributors
|
||||
|
||||
Mininet is an open source project and we gratefully acknowledge
|
||||
the many contributions to the project! If you have contributed
|
||||
code into the project and are not on this list, please let us know
|
||||
or send a pull request.
|
||||
|
||||
Contributors include:
|
||||
|
||||
Mininet Core Team (and alumni)
|
||||
|
||||
Bob Lantz
|
||||
Brandon Heller
|
||||
Nikhil Handigol
|
||||
Vimal Jeyakumar
|
||||
Brian O'Connor
|
||||
Cody Burkard
|
||||
|
||||
Additional Mininet Contributors
|
||||
|
||||
Tomasz Buchert
|
||||
Gustavo Pantuza Coelho Pinto
|
||||
Fernando Cappi
|
||||
Ryan Cox
|
||||
Shaun Crampton
|
||||
David Erickson
|
||||
Glen Gibb
|
||||
Andrew Ferguson
|
||||
Eder Leao Fernandes
|
||||
Gregory Gee
|
||||
Jon Hall
|
||||
Roan Huang
|
||||
Vitaly Ivanov
|
||||
Babis Kaidos
|
||||
Rich Lane
|
||||
Rémy Léone
|
||||
Zi Shen Lim
|
||||
David Mahler
|
||||
Murphy McCauley
|
||||
José Pedro Oliveira
|
||||
James Page
|
||||
Angad Singh
|
||||
Piyush Srivastava
|
||||
Ed Swierk
|
||||
Darshan Thaker
|
||||
Andreas Wundsam
|
||||
Isaku Yamahata
|
||||
Baohua Yang
|
||||
|
||||
Thanks also to everyone who has submitted issues and pull
|
||||
requests on github, and to our friendly mininet-discuss
|
||||
mailing list!
|
||||
@@ -2,7 +2,7 @@
|
||||
Mininet Installation/Configuration Notes
|
||||
----------------------------------------
|
||||
|
||||
Mininet 2.0.0
|
||||
Mininet 2.3.0d4
|
||||
---
|
||||
|
||||
The supported installation methods for Mininet are 1) using a
|
||||
@@ -15,7 +15,7 @@ like to contribute an installation script, we would welcome it!)
|
||||
1. Easiest "installation" - use our pre-built VM image!
|
||||
|
||||
The easiest way to get Mininet running is to start with one of our
|
||||
pre-built virtual machine images from <http://openflow.org/mininet>
|
||||
pre-built virtual machine images from <http://mininet.org/>
|
||||
|
||||
Boot up the VM image, log in, and follow the instructions on the
|
||||
Mininet web site.
|
||||
@@ -42,21 +42,38 @@ like to contribute an installation script, we would welcome it!)
|
||||
sudo rm /usr/local/bin/ovs*
|
||||
sudo rm /usr/local/sbin/ovs*
|
||||
|
||||
3. Native installation from source on Ubuntu 11.10+
|
||||
3. Native installation from source
|
||||
|
||||
3.1. Native installation from source on Ubuntu 12.04+
|
||||
|
||||
If you're reading this, you've probably already done so, but the
|
||||
command to download the Mininet source code is:
|
||||
|
||||
git clone git://github.com/mininet/mininet.git
|
||||
|
||||
If you are running Ubuntu, you may be able to use our handy
|
||||
`install.sh` script, which is in `mininet/util`.
|
||||
Note that the above git command will check out the latest and greatest
|
||||
Mininet (which we recommend!) If you want to run the last tagged/released
|
||||
version of Mininet, you can look at the release tags using
|
||||
|
||||
cd mininet
|
||||
git tag
|
||||
|
||||
and then
|
||||
|
||||
git checkout <release tag>
|
||||
|
||||
where <release tag> is the release you want to check out.
|
||||
|
||||
If you are running Ubuntu, Debian, or Fedora, you may be able to use
|
||||
our handy `install.sh` script, which is in `util/`.
|
||||
|
||||
*WARNING: USE AT YOUR OWN RISK!*
|
||||
|
||||
`install.sh` is a bit intrusive and may possibly damage your OS
|
||||
and/or home directory, by creating/modifying several directories
|
||||
such as `mininet`, `openflow`, `oftest`, `pox`, or `noxcosre`.
|
||||
such as `mininet`, `openflow`, `oftest`, `pox`, etc.. We recommend
|
||||
trying it in a VM before trying it on a system you use from day to day.
|
||||
|
||||
Although we hope it won't do anything completely terrible, you may
|
||||
want to look at the script before you run it, and you should make
|
||||
sure your system and home directory are backed up just in case!
|
||||
@@ -64,7 +81,7 @@ like to contribute an installation script, we would welcome it!)
|
||||
To install Mininet itself, the OpenFlow reference implementation, and
|
||||
Open vSwitch, you may use:
|
||||
|
||||
mininet/util/install.sh -fnv
|
||||
util/install.sh -fnv
|
||||
|
||||
This should be reasonably quick, and the following command should
|
||||
work after the installation:
|
||||
@@ -73,14 +90,55 @@ like to contribute an installation script, we would welcome it!)
|
||||
|
||||
To install ALL of the software which we use for OpenFlow tutorials,
|
||||
including POX, the OpenFlow WireShark dissector, the `oftest`
|
||||
framework, and other potentially useful software (and to add some
|
||||
stuff to `/etc/sysctl.conf` which may or may not be useful) you may
|
||||
use:
|
||||
framework, and other potentially useful software, you may use:
|
||||
|
||||
mininet/util/install.sh -a
|
||||
util/install.sh -a
|
||||
|
||||
This takes about 4 minutes on our test system.
|
||||
|
||||
You can change the directory where the dependencies are installed using
|
||||
the -s <directory> flag.
|
||||
|
||||
util/install.sh -s <directory> -a
|
||||
|
||||
3.2. Native installation from source on Fedora 18+.
|
||||
|
||||
As root execute the following operations:
|
||||
|
||||
* install git
|
||||
|
||||
yum install git
|
||||
|
||||
* create an user account (e.g. mininet) and add it to the wheel group
|
||||
|
||||
useradd [...] mininet
|
||||
usermod -a -G wheel mininet
|
||||
|
||||
* change the SElinux setting to permissive. It can be done
|
||||
temporarily with:
|
||||
|
||||
setenforce 0
|
||||
|
||||
then login with the new account (e.g. mininet) and do the following:
|
||||
|
||||
* clone the Mininet repository
|
||||
|
||||
git clone git://github.com/mininet/mininet.git
|
||||
|
||||
* install Mininet, the OpenFlow reference implementation, and
|
||||
Open vSwitch
|
||||
|
||||
util/install.sh -fnv
|
||||
|
||||
* enable and start openvswitch
|
||||
|
||||
sudo systemctl enable openvswitch
|
||||
sudo systemctl start openvswitch
|
||||
|
||||
* test the mininet installation
|
||||
|
||||
sudo mn --test pingall
|
||||
|
||||
4. Creating your own Mininet/OpenFlow tutorial VM
|
||||
|
||||
Creating your own Ubuntu Mininet VM for use with the OpenFlow tutorial
|
||||
@@ -103,12 +161,10 @@ like to contribute an installation script, we would welcome it!)
|
||||
|
||||
* A Linux kernel compiled with network namespace support enabled
|
||||
|
||||
* An OpenFlow implementation (either the reference user or kernel
|
||||
space implementations, or Open vSwitch.) Appropriate kernel
|
||||
modules (e.g. tun and ofdatapath for the reference kernel
|
||||
implementation) must be loaded.
|
||||
* An compatible software switch such as Open vSwitch or
|
||||
the Linux bridge.
|
||||
|
||||
* Python, `bash`, `ping`, `iperf`, etc.`
|
||||
* Python, `bash`, `ping`, `iperf`, etc.
|
||||
|
||||
* Root privileges (required for network device access)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Mininet 2.0.0 License
|
||||
Mininet 2.3.0d4 License
|
||||
|
||||
Copyright (c) 2012 Open Networking Laboratory
|
||||
Copyright (c) 2013-2018 Open Networking Laboratory
|
||||
Copyright (c) 2009-2012 Bob Lantz and The Board of Trustees of
|
||||
The Leland Stanford Junior University
|
||||
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
MININET = mininet/*.py
|
||||
TEST = mininet/test/*.py
|
||||
EXAMPLES = examples/*.py
|
||||
EXAMPLES = mininet/examples/*.py
|
||||
MN = bin/mn
|
||||
PYTHON ?= python
|
||||
PYMN = $(PYTHON) -B bin/mn
|
||||
BIN = $(MN)
|
||||
PYSRC = $(MININET) $(TEST) $(EXAMPLES) $(BIN)
|
||||
MNEXEC = mnexec
|
||||
MANPAGES = mn.1 mnexec.1
|
||||
P8IGN = E251,E201,E302,E202
|
||||
BINDIR = /usr/bin
|
||||
MANDIR = /usr/share/man/man1
|
||||
P8IGN = E251,E201,E302,E202,E126,E127,E203,E226
|
||||
PREFIX ?= /usr
|
||||
BINDIR ?= $(PREFIX)/bin
|
||||
MANDIR ?= $(PREFIX)/share/man/man1
|
||||
DOCDIRS = doc/html doc/latex
|
||||
PDF = doc/latex/refman.pdf
|
||||
|
||||
CFLAGS += -Wall -Wextra
|
||||
|
||||
all: codecheck test
|
||||
|
||||
clean:
|
||||
@@ -22,7 +27,8 @@ codecheck: $(PYSRC)
|
||||
util/versioncheck.py
|
||||
pyflakes $(PYSRC)
|
||||
pylint --rcfile=.pylint $(PYSRC)
|
||||
pep8 --repeat --ignore=$(P8IGN) $(PYSRC)
|
||||
# Exclude miniedit from pep8 checking for now
|
||||
pep8 --repeat --ignore=$(P8IGN) `ls $(PYSRC) | grep -v miniedit.py`
|
||||
|
||||
errcheck: $(PYSRC)
|
||||
-echo "Running check for errors only"
|
||||
@@ -34,29 +40,38 @@ test: $(MININET) $(TEST)
|
||||
mininet/test/test_nets.py
|
||||
mininet/test/test_hifi.py
|
||||
|
||||
mnexec: mnexec.c $(MN) mininet/net.py
|
||||
cc $(CFLAGS) $(LDFLAGS) -DVERSION=\"`PYTHONPATH=. $(MN) --version`\" $< -o $@
|
||||
slowtest: $(MININET)
|
||||
-echo "Running slower tests (walkthrough, examples)"
|
||||
mininet/test/test_walkthrough.py -v
|
||||
mininet/examples/test/runner.py -v
|
||||
|
||||
install: $(MNEXEC) $(MANPAGES)
|
||||
install $(MNEXEC) $(BINDIR)
|
||||
install $(MANPAGES) $(MANDIR)
|
||||
python setup.py install
|
||||
mnexec: mnexec.c $(MN) mininet/net.py
|
||||
cc $(CFLAGS) $(LDFLAGS) -DVERSION=\"`PYTHONPATH=. $(PYMN) --version`\" $< -o $@
|
||||
|
||||
install-mnexec: $(MNEXEC)
|
||||
install -D $(MNEXEC) $(BINDIR)/$(MNEXEC)
|
||||
|
||||
install-manpages: $(MANPAGES)
|
||||
install -D -t $(MANDIR) $(MANPAGES)
|
||||
|
||||
install: install-mnexec install-manpages
|
||||
$(PYTHON) setup.py install
|
||||
|
||||
develop: $(MNEXEC) $(MANPAGES)
|
||||
# Perhaps we should link these as well
|
||||
# Perhaps we should link these as well
|
||||
install $(MNEXEC) $(BINDIR)
|
||||
install $(MANPAGES) $(MANDIR)
|
||||
python setup.py develop
|
||||
$(PYTHON) setup.py develop
|
||||
|
||||
man: $(MANPAGES)
|
||||
|
||||
mn.1: $(MN)
|
||||
PYTHONPATH=. help2man -N -n "create a Mininet network." \
|
||||
--no-discard-stderr $< -o $@
|
||||
--no-discard-stderr "$(PYMN)" -o $@
|
||||
|
||||
mnexec.1: mnexec
|
||||
help2man -N -n "execution utility for Mininet." \
|
||||
-h "-h" -v "-v" --no-discard-stderr ./$< -o $@
|
||||
-h "-h" -v "-v" --no-discard-stderr ./$< -o $@
|
||||
|
||||
.PHONY: doc
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
Mininet: Rapid Prototyping for Software Defined Networks
|
||||
========================================================
|
||||
|
||||
*The best way to emulate almost any network on your laptop!*
|
||||
|
||||
Version 2.0.0
|
||||
Mininet 2.3.0d4
|
||||
|
||||
[![Build Status][1]](https://travis-ci.org/mininet/mininet)
|
||||
|
||||
### What is Mininet?
|
||||
|
||||
@@ -67,32 +67,27 @@ Mininet includes:
|
||||
|
||||
`mn -c`
|
||||
|
||||
### New features in 2.0.0
|
||||
### New features in this release
|
||||
|
||||
Mininet 2.0.0 is a major upgrade and provides
|
||||
a number of enhancements and new features, including:
|
||||
This is primarily a performance improvement and bug fix release.
|
||||
|
||||
* "Mininet-HiFi" functionality:
|
||||
- Batch startup has been implemented for Open vSwitch, improving
|
||||
startup performance.
|
||||
|
||||
* Link bandwidth limits using `tc` (`TCIntf` and `TCLink` classes)
|
||||
- OVS patch links have been implemented via OVSLink and --link ovs
|
||||
|
||||
* CPU isolation and bandwidth limits (`CPULimitedHost` class)
|
||||
Warning! These links have *serious limitations* compared to
|
||||
virtual Ethernet pairs: they are not attached to real Linux
|
||||
interfaces so you cannot use tcpdump or wireshark with them;
|
||||
they also cannot be used in long chains - we don't recommend more
|
||||
than 64 OVSLinks, for example --linear,64. However, they can offer
|
||||
significantly better performance than veth pairs, for certain
|
||||
configurations.
|
||||
|
||||
* Support for Open vSwitch 1.4+ (including Ubuntu OVS packages)
|
||||
- You can now easily install Mininet on a Raspberry Pi ;-)
|
||||
|
||||
* Debian packaging (and `apt-get install mininet` in Ubuntu 12.10)
|
||||
|
||||
* First-class Interface (`Intf`) and Link (`Link`) classes for easier
|
||||
extensibility
|
||||
|
||||
* An upgraded Topology (`Topo`) class which supports node and link
|
||||
customization
|
||||
|
||||
* Man pages for the `mn` and `mnexec` utilities.
|
||||
|
||||
[Since the API (most notably the topology) has changed, existing code
|
||||
that runs in Mininet 1.0 will need to be changed to run with Mininet
|
||||
2.0. This is the primary reason for the major version number change.]
|
||||
- Additional information for this release and previous releases
|
||||
may be found in the release notes on docs.mininet.org
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -103,7 +98,7 @@ See `INSTALL` for installation instructions and details.
|
||||
In addition to the API documentation (`make doc`), much useful
|
||||
information, including a Mininet walkthrough and an introduction
|
||||
to the Python API, is available on the
|
||||
[Mininet Web Site](http://openflow.org/mininet).
|
||||
[Mininet Web Site](http://mininet.org).
|
||||
There is also a wiki which you are encouraged to read and to
|
||||
contribute to, particularly the Frequently Asked Questions (FAQ.)
|
||||
|
||||
@@ -114,21 +109,24 @@ Mininet mailing list, `mininet-discuss` at:
|
||||
|
||||
<https://mailman.stanford.edu/mailman/listinfo/mininet-discuss>
|
||||
|
||||
### Contributing
|
||||
### Join Us
|
||||
|
||||
Mininet is an open-source project and is currently hosted at
|
||||
<https://github.com/mininet>. You are encouraged to download the code,
|
||||
examine it, modify it, and submit bug reports, bug fixes, feature
|
||||
requests, and enhancements!
|
||||
Thanks again to all of the Mininet contributors!
|
||||
|
||||
Mininet is an open source project and is currently hosted
|
||||
at <https://github.com/mininet>. You are encouraged to download
|
||||
the code, examine it, modify it, and submit bug reports, bug fixes,
|
||||
feature requests, new features and other issues and pull requests.
|
||||
Thanks to everyone who has contributed code to the Mininet project
|
||||
(see CONTRIBUTORS for more info!) It is because of everyone's
|
||||
hard work that Mininet continues to grow and improve.
|
||||
|
||||
### Enjoy Mininet
|
||||
|
||||
Best wishes, and we look forward to seeing what you can do with
|
||||
Mininet to change the networking world!
|
||||
|
||||
### Credits
|
||||
Bob Lantz
|
||||
Mininet Core Team
|
||||
|
||||
The Mininet Team:
|
||||
|
||||
* Bob Lantz
|
||||
* Brandon Heller
|
||||
* Nikhil Handigol
|
||||
* Vimal Jeyakumar
|
||||
[1]: https://travis-ci.org/mininet/mininet.svg?branch=master
|
||||
|
||||
@@ -21,83 +21,141 @@ if 'PYTHONPATH' in os.environ:
|
||||
sys.path = os.environ[ 'PYTHONPATH' ].split( ':' ) + sys.path
|
||||
|
||||
from mininet.clean import cleanup
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import lg, LEVELS, info
|
||||
import mininet.cli
|
||||
from mininet.log import lg, LEVELS, info, debug, warn, error, output
|
||||
from mininet.net import Mininet, MininetWithControlNet, VERSION
|
||||
from mininet.node import ( Host, CPULimitedHost, Controller, OVSController,
|
||||
NOX, RemoteController, UserSwitch, OVSKernelSwitch,
|
||||
OVSLegacyKernelSwitch )
|
||||
from mininet.link import Link, TCLink
|
||||
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import custom, customConstructor
|
||||
Ryu, NOX, RemoteController, findController,
|
||||
DefaultController, NullController,
|
||||
UserSwitch, OVSSwitch, OVSBridge,
|
||||
IVSSwitch )
|
||||
from mininet.nodelib import LinuxBridge
|
||||
from mininet.link import Link, TCLink, TCULink, OVSLink
|
||||
from mininet.topo import ( SingleSwitchTopo, LinearTopo,
|
||||
SingleSwitchReversedTopo, MinimalTopo )
|
||||
from mininet.topolib import TreeTopo, TorusTopo
|
||||
from mininet.util import customClass, specialClass, splitArgs
|
||||
from mininet.util import buildTopo
|
||||
|
||||
from functools import partial
|
||||
|
||||
# Experimental! cluster edition prototype
|
||||
from mininet.examples.cluster import ( MininetCluster, RemoteHost,
|
||||
RemoteOVSSwitch, RemoteLink,
|
||||
SwitchBinPlacer, RandomPlacer,
|
||||
ClusterCleanup )
|
||||
from mininet.examples.clustercli import ClusterCLI
|
||||
|
||||
PLACEMENT = { 'block': SwitchBinPlacer, 'random': RandomPlacer }
|
||||
|
||||
# built in topologies, created only when run
|
||||
TOPODEF = 'minimal'
|
||||
TOPOS = { 'minimal': lambda: SingleSwitchTopo( k=2 ),
|
||||
TOPOS = { 'minimal': MinimalTopo,
|
||||
'linear': LinearTopo,
|
||||
'reversed': SingleSwitchReversedTopo,
|
||||
'single': SingleSwitchTopo,
|
||||
'tree': TreeTopo }
|
||||
'tree': TreeTopo,
|
||||
'torus': TorusTopo }
|
||||
|
||||
SWITCHDEF = 'ovsk'
|
||||
SWITCHDEF = 'default'
|
||||
SWITCHES = { 'user': UserSwitch,
|
||||
'ovsk': OVSKernelSwitch,
|
||||
'ovsl': OVSLegacyKernelSwitch }
|
||||
'ovs': OVSSwitch,
|
||||
'ovsbr' : OVSBridge,
|
||||
# Keep ovsk for compatibility with 2.0
|
||||
'ovsk': OVSSwitch,
|
||||
'ivs': IVSSwitch,
|
||||
'lxbr': LinuxBridge,
|
||||
'default': OVSSwitch }
|
||||
|
||||
HOSTDEF = 'proc'
|
||||
HOSTS = { 'proc': Host,
|
||||
'rt': custom( CPULimitedHost, sched='rt' ),
|
||||
'cfs': custom( CPULimitedHost, sched='cfs' ) }
|
||||
'rt': specialClass( CPULimitedHost, defaults=dict( sched='rt' ) ),
|
||||
'cfs': specialClass( CPULimitedHost, defaults=dict( sched='cfs' ) ) }
|
||||
|
||||
CONTROLLERDEF = 'ovsc'
|
||||
CONTROLLERDEF = 'default'
|
||||
CONTROLLERS = { 'ref': Controller,
|
||||
'ovsc': OVSController,
|
||||
'nox': NOX,
|
||||
'remote': RemoteController,
|
||||
'none': lambda name: None }
|
||||
'ryu': Ryu,
|
||||
'default': DefaultController, # Note: overridden below
|
||||
'none': NullController }
|
||||
|
||||
LINKDEF = 'default'
|
||||
LINKS = { 'default': Link,
|
||||
'tc': TCLink }
|
||||
LINKS = { 'default': Link, # Note: overridden below
|
||||
'tc': TCLink,
|
||||
'tcu': TCULink,
|
||||
'ovs': OVSLink }
|
||||
|
||||
# TESTS dict can contain functions and/or Mininet() method names
|
||||
# XXX: it would be nice if we could specify a default test, but
|
||||
# this may be tricky
|
||||
TESTS = { name: True
|
||||
for name in ( 'pingall', 'pingpair', 'iperf', 'iperfudp' ) }
|
||||
|
||||
CLI = None # Set below if needed
|
||||
|
||||
# Locally defined tests
|
||||
def allTest( net ):
|
||||
"Run ping and iperf tests"
|
||||
net.waitConnected()
|
||||
net.start()
|
||||
net.ping()
|
||||
net.iperf()
|
||||
|
||||
def nullTest( _net ):
|
||||
"Null 'test' (does nothing)"
|
||||
pass
|
||||
|
||||
TESTS.update( all=allTest, none=nullTest, build=nullTest )
|
||||
|
||||
# Map to alternate spellings of Mininet() methods
|
||||
ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair',
|
||||
'iperfudp': 'iperfUdp' }
|
||||
|
||||
def runTests( mn, options ):
|
||||
"""Run tests
|
||||
mn: Mininet object
|
||||
option: list of test optinos """
|
||||
# Split option into test name and parameters
|
||||
for option in options:
|
||||
# Multiple tests may be separated by '+' for now
|
||||
for test in option.split( '+' ):
|
||||
test, args, kwargs = splitArgs( test )
|
||||
test = ALTSPELLING.get( test.lower(), test )
|
||||
testfn = TESTS.get( test, test )
|
||||
if callable( testfn ):
|
||||
testfn( mn, *args, **kwargs )
|
||||
elif hasattr( mn, test ):
|
||||
mn.waitConnected()
|
||||
getattr( mn, test )( *args, **kwargs )
|
||||
else:
|
||||
raise Exception( 'Test %s is unknown - please specify one of '
|
||||
'%s ' % ( test, TESTS.keys() ) )
|
||||
|
||||
|
||||
# optional tests to run
|
||||
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
|
||||
'none' ]
|
||||
|
||||
ALTSPELLING = { 'pingall': 'pingAll',
|
||||
'pingpair': 'pingPair',
|
||||
'iperfudp': 'iperfUdp',
|
||||
'iperfUDP': 'iperfUdp' }
|
||||
|
||||
|
||||
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
|
||||
def addDictOption( opts, choicesDict, default, name, **kwargs ):
|
||||
"""Convenience function to add choices dicts to OptionParser.
|
||||
opts: OptionParser instance
|
||||
choicesDict: dictionary of valid choices, must include default
|
||||
default: default choice key
|
||||
name: long option name
|
||||
help: string"""
|
||||
if default not in choicesDict:
|
||||
raise Exception( 'Invalid default %s for choices dict: %s' %
|
||||
( default, name ) )
|
||||
if not helpStr:
|
||||
helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
|
||||
'[,param=value...]' )
|
||||
opts.add_option( '--' + name,
|
||||
type='string',
|
||||
default = default,
|
||||
help = helpStr )
|
||||
|
||||
kwargs: additional arguments to add_option"""
|
||||
helpStr = ( '|'.join( sorted( choicesDict.keys() ) ) +
|
||||
'[,param=value...]' )
|
||||
helpList = [ '%s=%s' % ( k, v.__name__ )
|
||||
for k, v in choicesDict.items() ]
|
||||
helpStr += ' ' + ( ' '.join( helpList ) )
|
||||
params = dict( type='string', default=default, help=helpStr )
|
||||
params.update( **kwargs )
|
||||
opts.add_option( '--' + name, **params )
|
||||
|
||||
def version( *_args ):
|
||||
"Print Mininet version and exit"
|
||||
print "%s" % VERSION
|
||||
output( "%s\n" % VERSION )
|
||||
exit()
|
||||
|
||||
|
||||
class MininetRunner( object ):
|
||||
"Build, setup, and run Mininet."
|
||||
|
||||
@@ -111,9 +169,35 @@ class MininetRunner( object ):
|
||||
self.setup()
|
||||
self.begin()
|
||||
|
||||
def custom( self, _option, _opt_str, value, _parser ):
|
||||
"""Parse custom file and add params.
|
||||
option: option e.g. --custom
|
||||
opt_str: option string e.g. --custom
|
||||
value: the value the follows the option
|
||||
parser: option parser instance"""
|
||||
files = []
|
||||
if os.path.isfile( value ):
|
||||
# Accept any single file (including those with commas)
|
||||
files.append( value )
|
||||
else:
|
||||
# Accept a comma-separated list of filenames
|
||||
files += value.split(',')
|
||||
|
||||
for fileName in files:
|
||||
customs = {}
|
||||
if os.path.isfile( fileName ):
|
||||
# pylint: disable=exec-used
|
||||
exec( compile( open( fileName ).read(), fileName, 'exec' ),
|
||||
customs, customs )
|
||||
for name, val in customs.items():
|
||||
self.setCustom( name, val )
|
||||
else:
|
||||
raise Exception( 'could not find custom file: %s' % fileName )
|
||||
|
||||
def setCustom( self, name, value ):
|
||||
"Set custom parameters for MininetRunner."
|
||||
if name in ( 'topos', 'switches', 'hosts', 'controllers' ):
|
||||
if name in ( 'topos', 'switches', 'hosts', 'controllers', 'links'
|
||||
'testnames', 'tests' ):
|
||||
# Update dictionaries
|
||||
param = name.upper()
|
||||
globals()[ param ].update( value )
|
||||
@@ -124,26 +208,23 @@ class MininetRunner( object ):
|
||||
# Add or modify global variable or class
|
||||
globals()[ name ] = value
|
||||
|
||||
def parseCustomFile( self, fileName ):
|
||||
"Parse custom file and add params before parsing cmd-line options."
|
||||
customs = {}
|
||||
if os.path.isfile( fileName ):
|
||||
execfile( fileName, customs, customs )
|
||||
for name, val in customs.iteritems():
|
||||
self.setCustom( name, val )
|
||||
def setNat( self, _option, opt_str, value, parser ):
|
||||
"Set NAT option(s)"
|
||||
assert self # satisfy pylint
|
||||
parser.values.nat = True
|
||||
# first arg, first char != '-'
|
||||
if parser.rargs and parser.rargs[ 0 ][ 0 ] != '-':
|
||||
value = parser.rargs.pop( 0 )
|
||||
_, args, kwargs = splitArgs( opt_str + ',' + value )
|
||||
parser.values.nat_args = args
|
||||
parser.values.nat_kwargs = kwargs
|
||||
else:
|
||||
raise Exception( 'could not find custom file: %s' % fileName )
|
||||
parser.values.nat_args = []
|
||||
parser.values.nat_kwargs = {}
|
||||
|
||||
def parseArgs( self ):
|
||||
"""Parse command-line args and return options object.
|
||||
returns: opts parse options dict"""
|
||||
if '--custom' in sys.argv:
|
||||
index = sys.argv.index( '--custom' )
|
||||
if len( sys.argv ) > index + 1:
|
||||
filename = sys.argv[ index + 1 ]
|
||||
self.parseCustomFile( filename )
|
||||
else:
|
||||
raise Exception( 'Custom file name not found' )
|
||||
|
||||
desc = ( "The %prog utility creates Mininet network from the\n"
|
||||
"command line. It can create parametrized topologies,\n"
|
||||
@@ -155,18 +236,19 @@ class MininetRunner( object ):
|
||||
opts = OptionParser( description=desc, usage=usage )
|
||||
addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
|
||||
addDictOption( opts, HOSTS, HOSTDEF, 'host' )
|
||||
addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
|
||||
addDictOption( opts, CONTROLLERS, [], 'controller', action='append' )
|
||||
addDictOption( opts, LINKS, LINKDEF, 'link' )
|
||||
addDictOption( opts, TOPOS, TOPODEF, 'topo' )
|
||||
|
||||
opts.add_option( '--clean', '-c', action='store_true',
|
||||
default=False, help='clean and exit' )
|
||||
opts.add_option( '--custom', type='string', default=None,
|
||||
help='read custom topo and node params from .py' +
|
||||
'file' )
|
||||
opts.add_option( '--test', type='choice', choices=TESTS,
|
||||
default=TESTS[ 0 ],
|
||||
help='|'.join( TESTS ) )
|
||||
opts.add_option( '--custom', action='callback',
|
||||
callback=self.custom,
|
||||
type='string',
|
||||
help='read custom classes or params from .py file(s)'
|
||||
)
|
||||
opts.add_option( '--test', default=[], action='append',
|
||||
dest='test', help='|'.join( TESTS.keys() ) )
|
||||
opts.add_option( '--xterms', '-x', action='store_true',
|
||||
default=False, help='spawn xterms for each node' )
|
||||
opts.add_option( '--ipbase', '-i', type='string', default='10.0.0.0/8',
|
||||
@@ -176,11 +258,11 @@ class MininetRunner( object ):
|
||||
opts.add_option( '--arp', action='store_true',
|
||||
default=False, help='set all-pairs ARP entries' )
|
||||
opts.add_option( '--verbosity', '-v', type='choice',
|
||||
choices=LEVELS.keys(), default = 'info',
|
||||
choices=list( LEVELS.keys() ), default = 'info',
|
||||
help = '|'.join( LEVELS.keys() ) )
|
||||
opts.add_option( '--innamespace', action='store_true',
|
||||
default=False, help='sw and ctrl in namespace?' )
|
||||
opts.add_option( '--listenport', type='int', default=6634,
|
||||
opts.add_option( '--listenport', type='int', default=6654,
|
||||
help='base port for passive switch listening' )
|
||||
opts.add_option( '--nolistenport', action='store_true',
|
||||
default=False, help="don't use passive listening " +
|
||||
@@ -192,79 +274,137 @@ class MininetRunner( object ):
|
||||
opts.add_option( '--pin', action='store_true',
|
||||
default=False, help="pin hosts to CPU cores "
|
||||
"(requires --host cfs or --host rt)" )
|
||||
opts.add_option( '--version', action='callback', callback=version )
|
||||
opts.add_option( '--nat', action='callback', callback=self.setNat,
|
||||
help="[option=val...] adds a NAT to the topology that"
|
||||
" connects Mininet hosts to the physical network."
|
||||
" Warning: This may route any traffic on the machine"
|
||||
" that uses Mininet's"
|
||||
" IP subnet into the Mininet network."
|
||||
" If you need to change"
|
||||
" Mininet's IP subnet, see the --ipbase option." )
|
||||
opts.add_option( '--version', action='callback', callback=version,
|
||||
help='prints the version and exits' )
|
||||
opts.add_option( '--cluster', type='string', default=None,
|
||||
metavar='server1,server2...',
|
||||
help=( 'run on multiple servers (experimental!)' ) )
|
||||
opts.add_option( '--placement', type='choice',
|
||||
choices=list( PLACEMENT.keys() ), default='block',
|
||||
metavar='block|random',
|
||||
help=( 'node placement for --cluster '
|
||||
'(experimental!) ' ) )
|
||||
|
||||
self.options, self.args = opts.parse_args()
|
||||
|
||||
# We don't accept extra arguments after the options
|
||||
if self.args:
|
||||
opts.print_help()
|
||||
exit()
|
||||
|
||||
def setup( self ):
|
||||
"Setup and validate environment."
|
||||
|
||||
# set logging verbosity
|
||||
if LEVELS[self.options.verbosity] > LEVELS['output']:
|
||||
print ( '*** WARNING: selected verbosity level (%s) will hide CLI '
|
||||
warn( '*** WARNING: selected verbosity level (%s) will hide CLI '
|
||||
'output!\n'
|
||||
'Please restart Mininet with -v [debug, info, output].'
|
||||
'Please restart Mininet with -v [debug, info, output].\n'
|
||||
% self.options.verbosity )
|
||||
lg.setLogLevel( self.options.verbosity )
|
||||
|
||||
# Maybe we'll reorganize this someday...
|
||||
# pylint: disable=too-many-branches,too-many-statements,global-statement
|
||||
|
||||
def begin( self ):
|
||||
"Create and run mininet."
|
||||
|
||||
if self.options.clean:
|
||||
global CLI
|
||||
|
||||
opts = self.options
|
||||
|
||||
if opts.cluster:
|
||||
servers = opts.cluster.split( ',' )
|
||||
for server in servers:
|
||||
ClusterCleanup.add( server )
|
||||
|
||||
if opts.clean:
|
||||
cleanup()
|
||||
exit()
|
||||
|
||||
start = time.time()
|
||||
|
||||
topo = buildTopo( TOPOS, self.options.topo )
|
||||
switch = customConstructor( SWITCHES, self.options.switch )
|
||||
host = customConstructor( HOSTS, self.options.host )
|
||||
controller = customConstructor( CONTROLLERS, self.options.controller )
|
||||
link = customConstructor( LINKS, self.options.link )
|
||||
if not opts.controller:
|
||||
# Update default based on available controllers
|
||||
CONTROLLERS[ 'default' ] = findController()
|
||||
opts.controller = [ 'default' ]
|
||||
if not CONTROLLERS[ 'default' ]:
|
||||
opts.controller = [ 'none' ]
|
||||
if opts.switch == 'default':
|
||||
info( '*** No default OpenFlow controller found '
|
||||
'for default switch!\n' )
|
||||
info( '*** Falling back to OVS Bridge\n' )
|
||||
opts.switch = 'ovsbr'
|
||||
elif opts.switch not in ( 'ovsbr', 'lxbr' ):
|
||||
raise Exception( "Could not find a default controller "
|
||||
"for switch %s" %
|
||||
opts.switch )
|
||||
|
||||
topo = buildTopo( TOPOS, opts.topo )
|
||||
switch = customClass( SWITCHES, opts.switch )
|
||||
host = customClass( HOSTS, opts.host )
|
||||
controller = [ customClass( CONTROLLERS, c )
|
||||
for c in opts.controller ]
|
||||
|
||||
if opts.switch == 'user' and opts.link == 'default':
|
||||
debug( '*** Using TCULink with UserSwitch\n' )
|
||||
# Use link configured correctly for UserSwitch
|
||||
opts.link = 'tcu'
|
||||
|
||||
link = customClass( LINKS, opts.link )
|
||||
|
||||
if self.validate:
|
||||
self.validate( self.options )
|
||||
self.validate( opts )
|
||||
|
||||
if opts.nolistenport:
|
||||
opts.listenport = None
|
||||
|
||||
# Handle innamespace, cluster options
|
||||
if opts.innamespace and opts.cluster:
|
||||
error( "Please specify --innamespace OR --cluster\n" )
|
||||
exit()
|
||||
Net = MininetWithControlNet if opts.innamespace else Mininet
|
||||
if opts.cluster:
|
||||
warn( '*** WARNING: Experimental cluster mode!\n'
|
||||
'*** Using RemoteHost, RemoteOVSSwitch, RemoteLink\n' )
|
||||
host, switch, link = RemoteHost, RemoteOVSSwitch, RemoteLink
|
||||
Net = partial( MininetCluster, servers=servers,
|
||||
placement=PLACEMENT[ opts.placement ] )
|
||||
mininet.cli.CLI = ClusterCLI
|
||||
|
||||
inNamespace = self.options.innamespace
|
||||
Net = MininetWithControlNet if inNamespace else Mininet
|
||||
ipBase = self.options.ipbase
|
||||
xterms = self.options.xterms
|
||||
mac = self.options.mac
|
||||
arp = self.options.arp
|
||||
pin = self.options.pin
|
||||
listenPort = None
|
||||
if not self.options.nolistenport:
|
||||
listenPort = self.options.listenport
|
||||
mn = Net( topo=topo,
|
||||
switch=switch, host=host, controller=controller,
|
||||
link=link,
|
||||
ipBase=ipBase,
|
||||
inNamespace=inNamespace,
|
||||
xterms=xterms, autoSetMacs=mac,
|
||||
autoStaticArp=arp, autoPinCpus=pin,
|
||||
listenPort=listenPort )
|
||||
switch=switch, host=host, controller=controller, link=link,
|
||||
ipBase=opts.ipbase, inNamespace=opts.innamespace,
|
||||
xterms=opts.xterms, autoSetMacs=opts.mac,
|
||||
autoStaticArp=opts.arp, autoPinCpus=opts.pin,
|
||||
listenPort=opts.listenport )
|
||||
|
||||
if self.options.pre:
|
||||
CLI( mn, script=self.options.pre )
|
||||
if opts.ensure_value( 'nat', False ):
|
||||
mn.addNAT( *opts.nat_args, **opts.nat_kwargs ).configDefault()
|
||||
|
||||
test = self.options.test
|
||||
test = ALTSPELLING.get( test, test )
|
||||
# --custom files can set CLI or change mininet.cli.CLI
|
||||
CLI = mininet.cli.CLI if CLI is None else CLI
|
||||
|
||||
if opts.pre:
|
||||
CLI( mn, script=opts.pre )
|
||||
|
||||
mn.start()
|
||||
|
||||
if test == 'none':
|
||||
pass
|
||||
elif test == 'all':
|
||||
mn.start()
|
||||
mn.ping()
|
||||
mn.iperf()
|
||||
elif test == 'cli':
|
||||
if opts.test:
|
||||
runTests( mn, opts.test )
|
||||
else:
|
||||
CLI( mn )
|
||||
elif test != 'build':
|
||||
getattr( mn, test )()
|
||||
|
||||
if self.options.post:
|
||||
CLI( mn, script=self.options.post )
|
||||
if opts.post:
|
||||
CLI( mn, script=opts.post )
|
||||
|
||||
mn.stop()
|
||||
|
||||
@@ -273,4 +413,21 @@ class MininetRunner( object ):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
MininetRunner()
|
||||
try:
|
||||
MininetRunner()
|
||||
except KeyboardInterrupt:
|
||||
info( "\n\nKeyboard Interrupt. Shutting down and cleaning up...\n\n")
|
||||
cleanup()
|
||||
except Exception:
|
||||
# Print exception
|
||||
type_, val_, trace_ = sys.exc_info()
|
||||
errorMsg = ( "-"*80 + "\n" +
|
||||
"Caught exception. Cleaning up...\n\n" +
|
||||
"%s: %s\n" % ( type_.__name__, val_ ) +
|
||||
"-"*80 + "\n" )
|
||||
error( errorMsg )
|
||||
# Print stack trace to debug log
|
||||
import traceback
|
||||
stackTrace = traceback.format_exc()
|
||||
debug( stackTrace + "\n" )
|
||||
cleanup()
|
||||
|
||||
Vendored
+20
@@ -1,3 +1,23 @@
|
||||
mininet (2.1.0-0ubuntu1) saucy; urgency=low
|
||||
|
||||
* Add 2.1.0 final packaging
|
||||
|
||||
-- Bob Lantz <rlantz@cs.stanford.edu> Wed, 18 Sep 2013 22:43:47 -0700
|
||||
|
||||
mininet (2.1.0~rc1-0ubuntu1) saucy; urgency=low
|
||||
|
||||
* New upstream release candidate:
|
||||
- d/control: Drop dependency on python-networkx, add iperf, socat
|
||||
and cgroup-bin to Depends.
|
||||
|
||||
-- James Page <james.page@ubuntu.com> Wed, 28 Aug 2013 10:10:20 +0100
|
||||
|
||||
mininet (2.0.0-0ubuntu1) raring; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- James Page <james.page@ubuntu.com> Wed, 19 Dec 2012 15:48:01 +0000
|
||||
|
||||
mininet (2.0.0~rc1-0ubuntu1) quantal; urgency=low
|
||||
|
||||
* New upstream release.
|
||||
|
||||
Vendored
+5
-4
@@ -9,20 +9,21 @@ Build-Depends:
|
||||
help2man,
|
||||
python-dev,
|
||||
python-pkg-resources,
|
||||
python-setuptools,
|
||||
python-networkx
|
||||
python-setuptools
|
||||
Homepage: http://openflow.org/mininet
|
||||
|
||||
Package: mininet
|
||||
Architecture: any
|
||||
Depends:
|
||||
openvswitch-switch,
|
||||
python-networkx,
|
||||
telnet,
|
||||
socat,
|
||||
iperf,
|
||||
cgroup-bin,
|
||||
${misc:Depends},
|
||||
${python:Depends},
|
||||
${shlibs:Depends}
|
||||
Recommends: iperf, openvswitch-controller, socat
|
||||
Recommends: openvswitch-controller
|
||||
Description: Process-based network emulator
|
||||
Mininet is a network emulator which uses lightweight
|
||||
virtualization to create virtual networks for rapid
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ Upstream-Name: mininet
|
||||
Source: https://github.com/mininet/mininet
|
||||
|
||||
Files: *
|
||||
Copyright: 2012 Open Networking Laboratory,
|
||||
Copyright: 2012-2013 Open Networking Laboratory,
|
||||
2009-2012 Bob Lantz,
|
||||
2009-2012 The Board of Trustees of the Leland Stanford Junior
|
||||
University
|
||||
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
|
||||
Mininet Examples
|
||||
|
||||
These examples are intended to help you get started using
|
||||
Mininet's Python API.
|
||||
|
||||
---
|
||||
|
||||
baresshd.py:
|
||||
|
||||
This example uses Mininet's medium-level API to create an sshd
|
||||
process running in a namespace. Doesn't use OpenFlow.
|
||||
|
||||
consoles.py:
|
||||
|
||||
This example creates a grid of console windows, one for each node,
|
||||
and allows interaction with and monitoring of each console, including
|
||||
graphical monitoring.
|
||||
|
||||
controllers.py:
|
||||
|
||||
This example creates a network with multiple controllers, by
|
||||
using a custom Switch() subclass.
|
||||
|
||||
controllers2.py:
|
||||
|
||||
This example creates a network with multiple controllers by
|
||||
creating an empty network, adding nodes to it, and manually
|
||||
starting the switches.
|
||||
|
||||
cpu.py:
|
||||
|
||||
This example tests iperf bandwidth for varying CPU limits.
|
||||
|
||||
emptynet.py:
|
||||
|
||||
This example demonstrates creating an empty network (i.e. with no
|
||||
topology object) and adding nodes to it.
|
||||
|
||||
hwintf.py:
|
||||
|
||||
This example shows how to add an interface (for example a real
|
||||
hardware interface) to a network after the network is created.
|
||||
|
||||
limit.py:
|
||||
|
||||
This example shows how to use link and CPU limits.
|
||||
|
||||
linearbandwidth.py:
|
||||
|
||||
This example shows how to create a custom topology programatically
|
||||
by subclassing Topo, and how to run a series of tests on it.
|
||||
|
||||
miniedit.py:
|
||||
|
||||
This example demonstrates creating a network via a graphical editor.
|
||||
|
||||
multiping.py:
|
||||
|
||||
This example demonstrates one method for
|
||||
monitoring output from multiple hosts, using node.monitor().
|
||||
|
||||
multipoll.py:
|
||||
|
||||
This example demonstrates monitoring output files from multiple hosts.
|
||||
|
||||
multitest.py:
|
||||
|
||||
This example creates a network and runs multiple tests on it.
|
||||
|
||||
popen.py:
|
||||
|
||||
This example monitors a number of hosts using host.popen() and
|
||||
pmonitor().
|
||||
|
||||
popenpoll.py:
|
||||
|
||||
This example demonstrates monitoring output from multiple hosts using
|
||||
the node.popen() interface (which returns Popen objects) and pmonitor().
|
||||
|
||||
scratchnet.py, scratchnetuser.py:
|
||||
|
||||
These two examples demonstrate how to create a network by using the lowest-
|
||||
level Mininet functions. Generally the higher-level API is easier to use,
|
||||
but scratchnet shows what is going on behind the scenes.
|
||||
|
||||
simpleperf.py:
|
||||
|
||||
A simple example of configuring network and CPU bandwidth limits.
|
||||
|
||||
sshd.py:
|
||||
|
||||
This example shows how to run an sshd process in each host, allowing
|
||||
you to log in via ssh. This requires connecting the Mininet data network
|
||||
to an interface in the root namespace (generaly the control network
|
||||
already lives in the root namespace, so it does not need to be explicitly
|
||||
connected.)
|
||||
|
||||
treeping64.py:
|
||||
|
||||
This example creates a 64-host tree network, and attempts to check full
|
||||
connectivity using ping, for different switch/datapath types.
|
||||
|
||||
tree1024.py:
|
||||
|
||||
This example attempts to create a 1024-host network, and then runs the
|
||||
CLI on it. It may run into scalability limits, depending on available
|
||||
memory and sysctl configuration (see INSTALL.)
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
Mininet Examples
|
||||
========================================================
|
||||
|
||||
These examples are intended to help you get started using
|
||||
Mininet's Python API.
|
||||
|
||||
========================================================
|
||||
|
||||
#### baresshd.py:
|
||||
|
||||
This example uses Mininet's medium-level API to create an sshd
|
||||
process running in a namespace. Doesn't use OpenFlow.
|
||||
|
||||
#### bind.py:
|
||||
|
||||
This example shows how you can create private directories for each
|
||||
node in a Mininet topology.
|
||||
|
||||
#### cluster.py:
|
||||
|
||||
This example contains all of the code for experimental cluster
|
||||
edition. Remote classes and MininetCluster can be imported from
|
||||
here to create a topology with nodes on remote machines.
|
||||
|
||||
#### clusterSanity.py:
|
||||
|
||||
This example runs cluster edition locally as a sanity check to test
|
||||
basic functionality.
|
||||
|
||||
#### clustercli.py:
|
||||
|
||||
This example contains a CLI for experimental cluster edition.
|
||||
|
||||
#### clusterdemo.py:
|
||||
|
||||
This example is a basic demo of cluster edition on 3 servers with
|
||||
a tree topology of depth 3 and fanout 3.
|
||||
|
||||
#### consoles.py:
|
||||
|
||||
This example creates a grid of console windows, one for each node,
|
||||
and allows interaction with and monitoring of each console, including
|
||||
graphical monitoring.
|
||||
|
||||
#### controllers.py:
|
||||
|
||||
This example creates a network with multiple controllers, by
|
||||
using a custom `Switch()` subclass.
|
||||
|
||||
#### controllers2.py:
|
||||
|
||||
This example creates a network with multiple controllers by
|
||||
creating an empty network, adding nodes to it, and manually
|
||||
starting the switches.
|
||||
|
||||
#### controlnet.py:
|
||||
|
||||
This examples shows how you can model the control network as well
|
||||
as the data network, by actually creating two Mininet objects.
|
||||
|
||||
#### cpu.py:
|
||||
|
||||
This example tests iperf bandwidth for varying CPU limits.
|
||||
|
||||
#### emptynet.py:
|
||||
|
||||
This example demonstrates creating an empty network (i.e. with no
|
||||
topology object) and adding nodes to it.
|
||||
|
||||
#### hwintf.py:
|
||||
|
||||
This example shows how to add an interface (for example a real
|
||||
hardware interface) to a network after the network is created.
|
||||
|
||||
#### intfoptions.py:
|
||||
|
||||
This example reconfigures a TCIntf during runtime with different
|
||||
traffic control commands to test bandwidth, loss, and delay.
|
||||
|
||||
#### limit.py:
|
||||
|
||||
This example shows how to use link and CPU limits.
|
||||
|
||||
#### linearbandwidth.py:
|
||||
|
||||
This example shows how to create a custom topology programatically
|
||||
by subclassing Topo, and how to run a series of tests on it.
|
||||
|
||||
#### linuxrouter.py:
|
||||
|
||||
This example shows how to create and configure a router in Mininet
|
||||
that uses Linux IP forwarding.
|
||||
|
||||
#### miniedit.py:
|
||||
|
||||
This example demonstrates creating a network via a graphical editor.
|
||||
|
||||
#### mobility.py:
|
||||
|
||||
This example demonstrates detaching an interface from one switch and
|
||||
attaching it another as a basic way to move a host around a network.
|
||||
|
||||
#### multiLink.py:
|
||||
|
||||
This example demonstrates the creation of multiple links between
|
||||
nodes using a custom Topology class.
|
||||
|
||||
#### multiping.py:
|
||||
|
||||
This example demonstrates one method for
|
||||
monitoring output from multiple hosts, using `node.monitor()`.
|
||||
|
||||
#### multipoll.py:
|
||||
|
||||
This example demonstrates monitoring output files from multiple hosts.
|
||||
|
||||
#### multitest.py:
|
||||
|
||||
This example creates a network and runs multiple tests on it.
|
||||
|
||||
#### nat.py:
|
||||
|
||||
This example shows how to connect a Mininet network to the Internet
|
||||
using NAT. It also answers the eternal question "why can't I ping
|
||||
`google.com`?"
|
||||
|
||||
#### natnet.py:
|
||||
|
||||
This example demonstrates how to create a network using a NAT node
|
||||
to connect hosts to the internet.
|
||||
|
||||
#### numberedports.py:
|
||||
|
||||
This example verifies the mininet ofport numbers match up to the ovs port numbers.
|
||||
It also verifies that the port numbers match up to the interface numbers
|
||||
|
||||
#### popen.py:
|
||||
|
||||
This example monitors a number of hosts using `host.popen()` and
|
||||
`pmonitor()`.
|
||||
|
||||
#### popenpoll.py:
|
||||
|
||||
This example demonstrates monitoring output from multiple hosts using
|
||||
the `node.popen()` interface (which returns `Popen` objects) and `pmonitor()`.
|
||||
|
||||
#### scratchnet.py, scratchnetuser.py:
|
||||
|
||||
These two examples demonstrate how to create a network by using the lowest-
|
||||
level Mininet functions. Generally the higher-level API is easier to use,
|
||||
but scratchnet shows what is going on behind the scenes.
|
||||
|
||||
#### simpleperf.py:
|
||||
|
||||
A simple example of configuring network and CPU bandwidth limits.
|
||||
|
||||
#### sshd.py:
|
||||
|
||||
This example shows how to run an `sshd` process in each host, allowing
|
||||
you to log in via `ssh`. This requires connecting the Mininet data network
|
||||
to an interface in the root namespace (generaly the control network
|
||||
already lives in the root namespace, so it does not need to be explicitly
|
||||
connected.)
|
||||
|
||||
#### tree1024.py:
|
||||
|
||||
This example attempts to create a 1024-host network, and then runs the
|
||||
CLI on it. It may run into scalability limits, depending on available
|
||||
memory and `sysctl` configuration (see `INSTALL`.)
|
||||
|
||||
#### treeping64.py:
|
||||
|
||||
This example creates a 64-host tree network, and attempts to check full
|
||||
connectivity using `ping`, for different switch/datapath types.
|
||||
|
||||
#### vlanhost.py:
|
||||
|
||||
An example of how to subclass Host to use a VLAN on its primary interface.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
"""
|
||||
Mininet Examples
|
||||
See README for details
|
||||
"""
|
||||
+23
-9
@@ -2,31 +2,45 @@
|
||||
|
||||
"This example doesn't use OpenFlow, but attempts to run sshd in a namespace."
|
||||
|
||||
import sys
|
||||
|
||||
from mininet.node import Host
|
||||
from mininet.util import ensureRoot
|
||||
from mininet.util import ensureRoot, waitListening
|
||||
from mininet.log import info, warn, output
|
||||
|
||||
|
||||
ensureRoot()
|
||||
timeout = 5
|
||||
|
||||
print "*** Creating nodes"
|
||||
info( "*** Creating nodes\n" )
|
||||
h1 = Host( 'h1' )
|
||||
|
||||
root = Host( 'root', inNamespace=False )
|
||||
|
||||
print "*** Creating links"
|
||||
info( "*** Creating link\n" )
|
||||
h1.linkTo( root )
|
||||
|
||||
print h1
|
||||
info( h1 )
|
||||
|
||||
print "*** Configuring nodes"
|
||||
info( "*** Configuring nodes\n" )
|
||||
h1.setIP( '10.0.0.1', 8 )
|
||||
root.setIP( '10.0.0.2', 8 )
|
||||
|
||||
print "*** Creating banner file"
|
||||
info( "*** Creating banner file\n" )
|
||||
f = open( '/tmp/%s.banner' % h1.name, 'w' )
|
||||
f.write( 'Welcome to %s at %s\n' % ( h1.name, h1.IP() ) )
|
||||
f.close()
|
||||
|
||||
print "*** Running sshd"
|
||||
h1.cmd( '/usr/sbin/sshd -o "Banner /tmp/%s.banner"' % h1.name )
|
||||
info( "*** Running sshd\n" )
|
||||
cmd = '/usr/sbin/sshd -o UseDNS=no -u0 -o "Banner /tmp/%s.banner"' % h1.name
|
||||
# add arguments from the command line
|
||||
if len( sys.argv ) > 1:
|
||||
cmd += ' ' + ' '.join( sys.argv[ 1: ] )
|
||||
h1.cmd( cmd )
|
||||
listening = waitListening( server=h1, port=22, timeout=timeout )
|
||||
|
||||
print "*** You may now ssh into", h1.name, "at", h1.IP()
|
||||
if listening:
|
||||
output( "*** You may now ssh into", h1.name, "at", h1.IP(), '\n' )
|
||||
else:
|
||||
warn( "*** Warning: after %s seconds, %s is not listening on port 22"
|
||||
% ( timeout, h1.name ), '\n' )
|
||||
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
bind.py: Bind mount example
|
||||
|
||||
This creates hosts with private directories that the user specifies.
|
||||
These hosts may have persistent directories that will be available
|
||||
across multiple mininet session, or temporary directories that will
|
||||
only last for one mininet session. To specify a persistent
|
||||
directory, add a tuple to a list of private directories:
|
||||
|
||||
[ ( 'directory to be mounted on', 'directory to be mounted' ) ]
|
||||
|
||||
String expansion may be used to create a directory template for
|
||||
each host. To do this, add a %(name)s in place of the host name
|
||||
when creating your list of directories:
|
||||
|
||||
[ ( '/var/run', '/tmp/%(name)s/var/run' ) ]
|
||||
|
||||
If no persistent directory is specified, the directories will default
|
||||
to temporary private directories. To do this, simply create a list of
|
||||
directories to be made private. A tmpfs will then be mounted on them.
|
||||
|
||||
You may use both temporary and persistent directories at the same
|
||||
time. In the following privateDirs string, each host will have a
|
||||
persistent directory in the root filesystem at
|
||||
"/tmp/(hostname)/var/run" mounted on "/var/run". Each host will also
|
||||
have a temporary private directory mounted on "/var/log".
|
||||
|
||||
[ ( '/var/run', '/tmp/%(name)s/var/run' ), '/var/log' ]
|
||||
|
||||
This example has both persistent directories mounted on '/var/log'
|
||||
and '/var/run'. It also has a temporary private directory mounted
|
||||
on '/var/mn'
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Host
|
||||
from mininet.cli import CLI
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from functools import partial
|
||||
|
||||
|
||||
# Sample usage
|
||||
|
||||
def testHostWithPrivateDirs():
|
||||
"Test bind mounts"
|
||||
topo = SingleSwitchTopo( 10 )
|
||||
privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ),
|
||||
( '/var/run', '/tmp/%(name)s/var/run' ),
|
||||
'/var/mn' ]
|
||||
host = partial( Host,
|
||||
privateDirs=privateDirs )
|
||||
net = Mininet( topo=topo, host=host )
|
||||
net.start()
|
||||
directories = [ directory[ 0 ] if isinstance( directory, tuple )
|
||||
else directory for directory in privateDirs ]
|
||||
info( 'Private Directories:', directories, '\n' )
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
testHostWithPrivateDirs()
|
||||
info( 'Done.\n')
|
||||
Executable
+1001
File diff suppressed because it is too large
Load Diff
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
A sanity check for cluster edition
|
||||
'''
|
||||
|
||||
from mininet.examples.cluster import MininetCluster
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.examples.clustercli import ClusterCLI as CLI
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
|
||||
def clusterSanity():
|
||||
"Sanity check for cluster mode"
|
||||
topo = SingleSwitchTopo()
|
||||
net = MininetCluster( topo=topo )
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
clusterSanity()
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"CLI for Mininet Cluster Edition prototype demo"
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import output, error
|
||||
|
||||
# pylint: disable=global-statement
|
||||
nx, graphviz_layout, plt = None, None, None # Will be imported on demand
|
||||
|
||||
|
||||
class ClusterCLI( CLI ):
|
||||
"CLI with additional commands for Cluster Edition demo"
|
||||
|
||||
@staticmethod
|
||||
def colorsFor( seq ):
|
||||
"Return a list of background colors for a sequence"
|
||||
colors = [ 'red', 'lightgreen', 'cyan', 'yellow', 'orange',
|
||||
'magenta', 'pink', 'grey', 'brown',
|
||||
'white' ]
|
||||
slen, clen = len( seq ), len( colors )
|
||||
reps = max( 1, slen / clen )
|
||||
colors = colors * reps
|
||||
colors = colors[ 0 : slen ]
|
||||
return colors
|
||||
|
||||
def do_plot( self, _line ):
|
||||
"Plot topology colored by node placement"
|
||||
# Import networkx if needed
|
||||
global nx, plt, graphviz_layout
|
||||
if not nx:
|
||||
try:
|
||||
# pylint: disable=import-error
|
||||
import networkx
|
||||
nx = networkx # satisfy pylint
|
||||
from matplotlib import pyplot
|
||||
plt = pyplot # satisfy pylint
|
||||
import pygraphviz
|
||||
assert pygraphviz # silence pyflakes
|
||||
# Networkx moved this around
|
||||
if hasattr( nx, 'graphviz_layout' ):
|
||||
graphviz_layout = nx.graphviz_layout
|
||||
else:
|
||||
graphviz_layout = nx.drawing.nx_agraph.graphviz_layout
|
||||
# pylint: enable=import-error
|
||||
except ImportError:
|
||||
error( 'plot requires networkx, matplotlib and pygraphviz - '
|
||||
'please install them and try again\n' )
|
||||
return
|
||||
# Make a networkx Graph
|
||||
g = nx.Graph()
|
||||
mn = self.mn
|
||||
servers = getattr( mn, 'servers', [ 'localhost' ] )
|
||||
hosts, switches = mn.hosts, mn.switches
|
||||
nodes = hosts + switches
|
||||
g.add_nodes_from( nodes )
|
||||
links = [ ( link.intf1.node, link.intf2.node )
|
||||
for link in self.mn.links ]
|
||||
g.add_edges_from( links )
|
||||
# Pick some shapes and colors
|
||||
# shapes = hlen * [ 's' ] + slen * [ 'o' ]
|
||||
color = dict( zip( servers, self.colorsFor( servers ) ) )
|
||||
# Plot it!
|
||||
pos = graphviz_layout( g )
|
||||
opts = { 'ax': None, 'font_weight': 'bold',
|
||||
'width': 2, 'edge_color': 'darkblue' }
|
||||
hcolors = [ color[ getattr( h, 'server', 'localhost' ) ]
|
||||
for h in hosts ]
|
||||
scolors = [ color[ getattr( s, 'server', 'localhost' ) ]
|
||||
for s in switches ]
|
||||
nx.draw_networkx( g, pos=pos, nodelist=hosts, node_size=800,
|
||||
label='host', node_color=hcolors, node_shape='s',
|
||||
**opts )
|
||||
nx.draw_networkx( g, pos=pos, nodelist=switches, node_size=1000,
|
||||
node_color=scolors, node_shape='o', **opts )
|
||||
# Get rid of axes, add title, and show
|
||||
fig = plt.gcf()
|
||||
ax = plt.gca()
|
||||
ax.get_xaxis().set_visible( False )
|
||||
ax.get_yaxis().set_visible( False )
|
||||
fig.canvas.set_window_title( 'Mininet')
|
||||
plt.title( 'Node Placement', fontweight='bold' )
|
||||
plt.show()
|
||||
|
||||
def do_status( self, _line ):
|
||||
"Report on node shell status"
|
||||
nodes = self.mn.hosts + self.mn.switches
|
||||
for node in nodes:
|
||||
node.shell.poll()
|
||||
exited = [ node for node in nodes
|
||||
if node.shell.returncode is not None ]
|
||||
if exited:
|
||||
for node in exited:
|
||||
output( '%s has exited with code %d\n'
|
||||
% ( node, node.shell.returncode ) )
|
||||
else:
|
||||
output( 'All nodes are still running.\n' )
|
||||
|
||||
def do_placement( self, _line ):
|
||||
"Describe node placement"
|
||||
mn = self.mn
|
||||
nodes = mn.hosts + mn.switches + mn.controllers
|
||||
for server in mn.servers:
|
||||
names = [ n.name for n in nodes if hasattr( n, 'server' )
|
||||
and n.server == server ]
|
||||
output( '%s: %s\n' % ( server, ' '.join( names ) ) )
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"clusterdemo.py: demo of Mininet Cluster Edition prototype"
|
||||
|
||||
from mininet.examples.cluster import ( MininetCluster, SwitchBinPlacer,
|
||||
RemoteLink )
|
||||
# ^ Could also use: RemoteSSHLink, RemoteGRELink
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.examples.clustercli import ClusterCLI as CLI
|
||||
|
||||
def demo():
|
||||
"Simple Demo of Cluster Mode"
|
||||
servers = [ 'localhost', 'ubuntu2', 'ubuntu3' ]
|
||||
topo = TreeTopo( depth=3, fanout=3 )
|
||||
net = MininetCluster( topo=topo, servers=servers, Link=RemoteLink,
|
||||
placement=SwitchBinPlacer )
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
demo()
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"clusterperf.py compare the maximum throughput between SSH and GRE tunnels"
|
||||
|
||||
from mininet.examples.cluster import RemoteSSHLink, RemoteGRELink, RemoteHost
|
||||
from mininet.net import Mininet
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
def perf(Link):
|
||||
"Test connectivity nand performance over Link"
|
||||
net = Mininet( host=RemoteHost, link=Link )
|
||||
h1 = net.addHost( 'h1')
|
||||
h2 = net.addHost( 'h2', server='ubuntu2' )
|
||||
net.addLink( h1, h2 )
|
||||
net.start()
|
||||
net.pingAll()
|
||||
net.iperf()
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
perf( RemoteSSHLink )
|
||||
perf( RemoteGRELink )
|
||||
+13
-3
@@ -315,11 +315,19 @@ class ConsoleApp( Frame ):
|
||||
|
||||
def updateGraph( self, _console, output ):
|
||||
"Update our graph."
|
||||
m = re.search( r'(\d+) Mbits/sec', output )
|
||||
m = re.search( r'(\d+.?\d*) ([KMG]?bits)/sec', output )
|
||||
if not m:
|
||||
return
|
||||
val, units = float( m.group( 1 ) ), m.group( 2 )
|
||||
#convert to Gbps
|
||||
if units[0] == 'M':
|
||||
val *= 10 ** -3
|
||||
elif units[0] == 'K':
|
||||
val *= 10 ** -6
|
||||
elif units[0] == 'b':
|
||||
val *= 10 ** -9
|
||||
self.updates += 1
|
||||
self.bw += .001 * float( m.group( 1 ) )
|
||||
self.bw += val
|
||||
if self.updates >= self.hostCount:
|
||||
self.graph.addBar( self.bw )
|
||||
self.bw = 0
|
||||
@@ -410,7 +418,9 @@ class ConsoleApp( Frame ):
|
||||
count = len( consoles )
|
||||
self.setOutputHook( self.updateGraph )
|
||||
for console in consoles:
|
||||
console.node.cmd( 'iperf -sD' )
|
||||
# Sometimes iperf -sD doesn't return,
|
||||
# so we run it in the background instead
|
||||
console.node.cmd( 'iperf -s &' )
|
||||
i = 0
|
||||
for console in consoles:
|
||||
i = ( i + 1 ) % count
|
||||
|
||||
+13
-5
@@ -6,13 +6,20 @@ different controllers, by creating a custom Switch() subclass.
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import OVSSwitch, Controller
|
||||
from mininet.node import OVSSwitch, Controller, RemoteController
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.cli import CLI
|
||||
|
||||
c0 = Controller( 'c0' )
|
||||
c1 = Controller( 'c1', ip='127.0.0.2' )
|
||||
cmap = { 's1': c0, 's2': c1, 's3': c1 }
|
||||
setLogLevel( 'info' )
|
||||
|
||||
# Two local and one "external" controller (which is actually c0)
|
||||
# Ignore the warning message that the remote isn't (yet) running
|
||||
c0 = Controller( 'c0', port=6633 )
|
||||
c1 = Controller( 'c1', port=6634 )
|
||||
c2 = RemoteController( 'c2', ip='127.0.0.1', port=6633 )
|
||||
|
||||
cmap = { 's1': c0, 's2': c1, 's3': c2 }
|
||||
|
||||
class MultiSwitch( OVSSwitch ):
|
||||
"Custom Switch() subclass that connects to different controllers"
|
||||
@@ -21,7 +28,8 @@ class MultiSwitch( OVSSwitch ):
|
||||
|
||||
topo = TreeTopo( depth=2, fanout=2 )
|
||||
net = Mininet( topo=topo, switch=MultiSwitch, build=False )
|
||||
net.controllers = [ c0, c1 ]
|
||||
for c in [ c0, c1 ]:
|
||||
net.addController(c)
|
||||
net.build()
|
||||
net.start()
|
||||
CLI( net )
|
||||
|
||||
+13
-12
@@ -11,49 +11,50 @@ Note that one could also create a custom switch class and pass it into
|
||||
the Mininet() constructor.
|
||||
"""
|
||||
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Controller, OVSSwitch
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
def multiControllerNet():
|
||||
"Create a network from semi-scratch with multiple controllers."
|
||||
|
||||
net = Mininet( controller=Controller, switch=OVSSwitch, build=False )
|
||||
net = Mininet( controller=Controller, switch=OVSSwitch )
|
||||
|
||||
print "*** Creating (reference) controllers"
|
||||
info( "*** Creating (reference) controllers\n" )
|
||||
c1 = net.addController( 'c1', port=6633 )
|
||||
c2 = net.addController( 'c2', port=6634 )
|
||||
|
||||
print "*** Creating switches"
|
||||
info( "*** Creating switches\n" )
|
||||
s1 = net.addSwitch( 's1' )
|
||||
s2 = net.addSwitch( 's2' )
|
||||
|
||||
print "*** Creating hosts"
|
||||
hosts1 = [ net.addHost( 'h%d' % n ) for n in 3, 4 ]
|
||||
hosts2 = [ net.addHost( 'h%d' % n ) for n in 5, 6 ]
|
||||
info( "*** Creating hosts\n" )
|
||||
hosts1 = [ net.addHost( 'h%d' % n ) for n in ( 3, 4 ) ]
|
||||
hosts2 = [ net.addHost( 'h%d' % n ) for n in ( 5, 6 ) ]
|
||||
|
||||
print "*** Creating links"
|
||||
info( "*** Creating links\n" )
|
||||
for h in hosts1:
|
||||
net.addLink( s1, h )
|
||||
for h in hosts2:
|
||||
net.addLink( s2, h )
|
||||
net.addLink( s1, s2 )
|
||||
|
||||
print "*** Starting network"
|
||||
info( "*** Starting network\n" )
|
||||
net.build()
|
||||
c1.start()
|
||||
c2.start()
|
||||
s1.start( [ c1 ] )
|
||||
s2.start( [ c2 ] )
|
||||
|
||||
print "*** Testing network"
|
||||
info( "*** Testing network\n" )
|
||||
net.pingAll()
|
||||
|
||||
print "*** Running CLI"
|
||||
info( "*** Running CLI\n" )
|
||||
CLI( net )
|
||||
|
||||
print "*** Stopping network"
|
||||
info( "*** Stopping network\n" )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
controlnet.py: Mininet with a custom control network
|
||||
|
||||
We create two Mininet() networks, a control network
|
||||
and a data network, running four DataControllers on the
|
||||
control network to control the data network.
|
||||
|
||||
Since we're using UserSwitch on the data network,
|
||||
it should correctly fail over to a backup controller.
|
||||
|
||||
We also use a Mininet Facade to talk to both the
|
||||
control and data networks from a single CLI.
|
||||
"""
|
||||
|
||||
from functools import partial
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Controller, UserSwitch
|
||||
from mininet.cli import CLI
|
||||
from mininet.topo import Topo
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
# Some minor hacks
|
||||
|
||||
class DataController( Controller ):
|
||||
"""Data Network Controller.
|
||||
patched to avoid checkListening error and to delete intfs"""
|
||||
|
||||
def checkListening( self ):
|
||||
"Ignore spurious error"
|
||||
pass
|
||||
|
||||
def stop( self, *args, **kwargs ):
|
||||
"Make sure intfs are deleted"
|
||||
kwargs.update( deleteIntfs=True )
|
||||
super( DataController, self ).stop( *args, **kwargs )
|
||||
|
||||
|
||||
class MininetFacade( object ):
|
||||
"""Mininet object facade that allows a single CLI to
|
||||
talk to one or more networks"""
|
||||
|
||||
def __init__( self, net, *args, **kwargs ):
|
||||
"""Create MininetFacade object.
|
||||
net: Primary Mininet object
|
||||
args: unnamed networks passed as arguments
|
||||
kwargs: named networks passed as arguments"""
|
||||
self.net = net
|
||||
self.nets = [ net ] + list( args ) + list( kwargs.values() )
|
||||
self.nameToNet = kwargs
|
||||
self.nameToNet['net'] = net
|
||||
|
||||
def __getattr__( self, name ):
|
||||
"returns attribute from Primary Mininet object"
|
||||
return getattr( self.net, name )
|
||||
|
||||
def __getitem__( self, key ):
|
||||
"returns primary/named networks or node from any net"
|
||||
#search kwargs for net named key
|
||||
if key in self.nameToNet:
|
||||
return self.nameToNet[ key ]
|
||||
#search each net for node named key
|
||||
for net in self.nets:
|
||||
if key in net:
|
||||
return net[ key ]
|
||||
|
||||
def __iter__( self ):
|
||||
"Iterate through all nodes in all Mininet objects"
|
||||
for net in self.nets:
|
||||
for node in net:
|
||||
yield node
|
||||
|
||||
def __len__( self ):
|
||||
"returns aggregate number of nodes in all nets"
|
||||
count = 0
|
||||
for net in self.nets:
|
||||
count += len(net)
|
||||
return count
|
||||
|
||||
def __contains__( self, key ):
|
||||
"returns True if node is a member of any net"
|
||||
return key in self.keys()
|
||||
|
||||
def keys( self ):
|
||||
"returns a list of all node names in all networks"
|
||||
return list( self )
|
||||
|
||||
def values( self ):
|
||||
"returns a list of all nodes in all networks"
|
||||
return [ self[ key ] for key in self ]
|
||||
|
||||
def items( self ):
|
||||
"returns (key,value) tuple list for every node in all networks"
|
||||
return zip( self.keys(), self.values() )
|
||||
|
||||
# A real control network!
|
||||
|
||||
class ControlNetwork( Topo ):
|
||||
"Control Network Topology"
|
||||
def __init__( self, n, dataController=DataController, **kwargs ):
|
||||
"""n: number of data network controller nodes
|
||||
dataController: class for data network controllers"""
|
||||
Topo.__init__( self, **kwargs )
|
||||
# Connect everything to a single switch
|
||||
cs0 = self.addSwitch( 'cs0' )
|
||||
# Add hosts which will serve as data network controllers
|
||||
for i in range( 0, n ):
|
||||
c = self.addHost( 'c%s' % i, cls=dataController,
|
||||
inNamespace=True )
|
||||
self.addLink( c, cs0 )
|
||||
# Connect switch to root namespace so that data network
|
||||
# switches will be able to talk to us
|
||||
root = self.addHost( 'root', inNamespace=False )
|
||||
self.addLink( root, cs0 )
|
||||
|
||||
|
||||
# Make it Happen!!
|
||||
|
||||
def run():
|
||||
"Create control and data networks, and invoke the CLI"
|
||||
|
||||
info( '* Creating Control Network\n' )
|
||||
ctopo = ControlNetwork( n=4, dataController=DataController )
|
||||
cnet = Mininet( topo=ctopo, ipBase='192.168.123.0/24', controller=None )
|
||||
info( '* Adding Control Network Controller\n')
|
||||
cnet.addController( 'cc0', controller=Controller )
|
||||
info( '* Starting Control Network\n')
|
||||
cnet.start()
|
||||
|
||||
info( '* Creating Data Network\n' )
|
||||
topo = TreeTopo( depth=2, fanout=2 )
|
||||
# UserSwitch so we can easily test failover
|
||||
sw = partial( UserSwitch, opts='--inactivity-probe=1 --max-backoff=1' )
|
||||
net = Mininet( topo=topo, switch=sw, controller=None )
|
||||
info( '* Adding Controllers to Data Network\n' )
|
||||
for host in cnet.hosts:
|
||||
if isinstance(host, Controller):
|
||||
net.addController( host )
|
||||
info( '* Starting Data Network\n')
|
||||
net.start()
|
||||
|
||||
mn = MininetFacade( net, cnet=cnet )
|
||||
|
||||
CLI( mn )
|
||||
|
||||
info( '* Stopping Data Network\n' )
|
||||
net.stop()
|
||||
|
||||
info( '* Stopping Control Network\n' )
|
||||
cnet.stop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
run()
|
||||
+58
-32
@@ -2,29 +2,40 @@
|
||||
|
||||
"""
|
||||
cpu.py: test iperf bandwidth for varying cpu limits
|
||||
|
||||
Since we are limiting the hosts (only), we should expect the iperf
|
||||
processes to be affected, as well as any system processing which is
|
||||
billed to the hosts.
|
||||
|
||||
We reserve >50% of cycles for system processing; we assume that
|
||||
this is enough for it not to affect results. Hosts are limited to
|
||||
40% of total cycles, which we assume is enough to make them CPU
|
||||
bound.
|
||||
|
||||
As CPU performance increases over time, we may have to reduce the
|
||||
overall CPU allocation so that the host processing is still CPU bound.
|
||||
This is perhaps an argument for specifying performance in a more
|
||||
system-independent manner.
|
||||
|
||||
It would also be nice to have a better handle on limiting packet
|
||||
processing cycles. It's not entirely clear to me how those are
|
||||
billed to user or system processes if we are using OVS with a kernel
|
||||
datapath. With a user datapath, they are easier to account for, but
|
||||
overall performance is usually lower.
|
||||
|
||||
Although the iperf client uses more CPU and should be CPU bound (?),
|
||||
we measure the received data at the server since the client transmit
|
||||
rate includes buffering.
|
||||
"""
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import custom
|
||||
from mininet.log import setLogLevel, output
|
||||
from mininet.util import custom, waitListening, decode
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.clean import cleanup
|
||||
|
||||
from time import sleep
|
||||
|
||||
def waitListening(client, server, port):
|
||||
"Wait until server is listening on port"
|
||||
if not client.cmd('which telnet'):
|
||||
raise Exception('Could not find telnet')
|
||||
cmd = ('sh -c "echo A | telnet -e A %s %s"' %
|
||||
(server.IP(), port))
|
||||
while 'Connected' not in client.cmd(cmd):
|
||||
output('waiting for', server,
|
||||
'to listen on port', port, '\n')
|
||||
sleep(.5)
|
||||
|
||||
|
||||
def bwtest( cpuLimits, period_us=100000, seconds=5 ):
|
||||
def bwtest( cpuLimits, period_us=100000, seconds=10 ):
|
||||
"""Example/test of link and CPU bandwidth limits
|
||||
cpu: cpu limit as fraction of overall CPU time"""
|
||||
|
||||
@@ -33,22 +44,36 @@ def bwtest( cpuLimits, period_us=100000, seconds=5 ):
|
||||
results = {}
|
||||
|
||||
for sched in 'rt', 'cfs':
|
||||
print '*** Testing with', sched, 'bandwidth limiting'
|
||||
info( '*** Testing with', sched, 'bandwidth limiting\n' )
|
||||
for cpu in cpuLimits:
|
||||
# cpu is the cpu fraction for all hosts, so we divide
|
||||
# it across two hosts
|
||||
host = custom( CPULimitedHost, sched=sched,
|
||||
period_us=period_us,
|
||||
cpu=cpu )
|
||||
net = Mininet( topo=topo, host=host )
|
||||
cpu=.5*cpu )
|
||||
try:
|
||||
net = Mininet( topo=topo, host=host )
|
||||
# pylint: disable=bare-except
|
||||
except:
|
||||
info( '*** Skipping scheduler %s and cleaning up\n' % sched )
|
||||
cleanup()
|
||||
break
|
||||
net.start()
|
||||
net.pingAll()
|
||||
hosts = [ net.getNodeByName( h ) for h in topo.hosts() ]
|
||||
client, server = hosts[ 0 ], hosts[ -1 ]
|
||||
server.cmd( 'iperf -s -p 5001 &' )
|
||||
info( '*** Starting iperf with %d%% of CPU allocated to hosts\n' %
|
||||
( 100.0 * cpu ) )
|
||||
# We measure at the server because it doesn't include
|
||||
# the client's buffer fill rate
|
||||
popen = server.popen( 'iperf -yc -s -p 5001' )
|
||||
waitListening( client, server, 5001 )
|
||||
result = client.cmd( 'iperf -yc -t %s -c %s' % (
|
||||
seconds, server.IP() ) ).split( ',' )
|
||||
# ignore empty result from waitListening/telnet
|
||||
popen.stdout.readline()
|
||||
client.cmd( 'iperf -yc -t %s -c %s' % ( seconds, server.IP() ) )
|
||||
result = decode( popen.stdout.readline() ).split( ',' )
|
||||
bps = float( result[ -1 ] )
|
||||
server.cmdPrint( 'kill %iperf' )
|
||||
popen.terminate()
|
||||
net.stop()
|
||||
updated = results.get( sched, [] )
|
||||
updated += [ ( cpu, bps ) ]
|
||||
@@ -60,22 +85,23 @@ def bwtest( cpuLimits, period_us=100000, seconds=5 ):
|
||||
def dump( results ):
|
||||
"Dump results"
|
||||
|
||||
fmt = '%s\t%s\t%s'
|
||||
fmt = '%s\t%s\t%s\n'
|
||||
|
||||
print
|
||||
print fmt % ( 'sched', 'cpu', 'client MB/s' )
|
||||
print
|
||||
info( '\n' )
|
||||
info( fmt % ( 'sched', 'cpu', 'received bits/sec' ) )
|
||||
|
||||
for sched in sorted( results.keys() ):
|
||||
entries = results[ sched ]
|
||||
for cpu, bps in entries:
|
||||
pct = '%.2f%%' % ( cpu * 100 )
|
||||
mbps = bps / 1e6
|
||||
print fmt % ( sched, pct, mbps )
|
||||
pct = '%d%%' % ( cpu * 100 )
|
||||
mbps = '%.2e' % bps
|
||||
info( fmt % ( sched, pct, mbps ) )
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
limits = [ .45, .4, .3, .2, .1 ]
|
||||
# These are the limits for the hosts/iperfs - the
|
||||
# rest is for system processes
|
||||
limits = [ .5, .4, .3, .2, .1 ]
|
||||
out = bwtest( limits )
|
||||
dump( out )
|
||||
|
||||
@@ -27,8 +27,8 @@ def emptyNet():
|
||||
s3 = net.addSwitch( 's3' )
|
||||
|
||||
info( '*** Creating links\n' )
|
||||
h1.linkTo( s3 )
|
||||
h2.linkTo( s3 )
|
||||
net.addLink( h1, s3 )
|
||||
net.addLink( h2, s3 )
|
||||
|
||||
info( '*** Starting network\n')
|
||||
net.start()
|
||||
|
||||
+8
-3
@@ -6,6 +6,7 @@ hardware interface) to a network after the network is created.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel, info, error
|
||||
@@ -16,10 +17,11 @@ from mininet.util import quietRun
|
||||
|
||||
def checkIntf( intf ):
|
||||
"Make sure intf exists and is not configured."
|
||||
if ( ' %s:' % intf ) not in quietRun( 'ip link show' ):
|
||||
config = quietRun( 'ifconfig %s 2>/dev/null' % intf, shell=True )
|
||||
if not config:
|
||||
error( 'Error:', intf, 'does not exist!\n' )
|
||||
exit( 1 )
|
||||
ips = re.findall( r'\d+\.\d+\.\d+\.\d+', quietRun( 'ifconfig ' + intf ) )
|
||||
ips = re.findall( r'\d+\.\d+\.\d+\.\d+', config )
|
||||
if ips:
|
||||
error( 'Error:', intf, 'has an IP address,'
|
||||
'and is probably in use!\n' )
|
||||
@@ -28,7 +30,10 @@ def checkIntf( intf ):
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
|
||||
intfName = 'eth1'
|
||||
# try to get hw intf from the command line; by default, use eth1
|
||||
intfName = sys.argv[ 1 ] if len( sys.argv ) > 1 else 'eth1'
|
||||
info( '*** Connecting to hw intf: %s' % intfName )
|
||||
|
||||
info( '*** Checking', intfName, '\n' )
|
||||
checkIntf( intfName )
|
||||
|
||||
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
'''
|
||||
example of using various TCIntf options.
|
||||
reconfigures a single interface using intf.config()
|
||||
to use different traffic control commands to test
|
||||
bandwidth, loss, and delay
|
||||
'''
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.link import TCLink
|
||||
|
||||
def intfOptions():
|
||||
"run various traffic control commands on a single interface"
|
||||
net = Mininet( autoStaticArp=True )
|
||||
net.addController( 'c0' )
|
||||
h1 = net.addHost( 'h1' )
|
||||
h2 = net.addHost( 'h2' )
|
||||
s1 = net.addSwitch( 's1' )
|
||||
link1 = net.addLink( h1, s1, cls=TCLink )
|
||||
net.addLink( h2, s1 )
|
||||
net.start()
|
||||
|
||||
# flush out latency from reactive forwarding delay
|
||||
net.pingAll()
|
||||
|
||||
info( '\n*** Configuring one intf with bandwidth of 5 Mb\n' )
|
||||
link1.intf1.config( bw=5 )
|
||||
info( '\n*** Running iperf to test\n' )
|
||||
net.iperf()
|
||||
|
||||
info( '\n*** Configuring one intf with loss of 50%\n' )
|
||||
link1.intf1.config( loss=50 )
|
||||
info( '\n' )
|
||||
net.iperf( ( h1, h2 ), l4Type='UDP' )
|
||||
|
||||
info( '\n*** Configuring one intf with delay of 15ms\n' )
|
||||
link1.intf1.config( delay='15ms' )
|
||||
info( '\n*** Run a ping to confirm delay\n' )
|
||||
net.pingPairFull()
|
||||
|
||||
info( '\n*** Done testing\n' )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
intfOptions()
|
||||
+13
-6
@@ -8,15 +8,14 @@ from mininet.net import Mininet
|
||||
from mininet.link import TCIntf
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.util import custom
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.util import custom, quietRun
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
|
||||
def testLinkLimit( net, bw ):
|
||||
"Run bandwidth limit test"
|
||||
print '*** Testing network %.2f Mbps bandwidth limit' % bw
|
||||
net.iperf( )
|
||||
|
||||
info( '*** Testing network %.2f Mbps bandwidth limit\n' % bw )
|
||||
net.iperf()
|
||||
|
||||
def limit( bw=10, cpu=.1 ):
|
||||
"""Example/test of link and CPU bandwidth limits
|
||||
@@ -25,7 +24,15 @@ def limit( bw=10, cpu=.1 ):
|
||||
intf = custom( TCIntf, bw=bw )
|
||||
myTopo = TreeTopo( depth=1, fanout=2 )
|
||||
for sched in 'rt', 'cfs':
|
||||
print '*** Testing with', sched, 'bandwidth limiting'
|
||||
info( '*** Testing with', sched, 'bandwidth limiting\n' )
|
||||
if sched == 'rt':
|
||||
release = quietRun( 'uname -r' ).strip('\r\n')
|
||||
output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s'
|
||||
% release )
|
||||
if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
|
||||
info( '*** RT Scheduler is not enabled in your kernel. '
|
||||
'Skipping this test\n' )
|
||||
continue
|
||||
host = custom( CPULimitedHost, sched=sched, cpu=cpu )
|
||||
net = Mininet( topo=myTopo, intf=intf, host=host )
|
||||
net.start()
|
||||
|
||||
+38
-22
@@ -23,11 +23,14 @@ of switches, this example demonstrates:
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch, Controller
|
||||
from mininet.topo import Topo
|
||||
from mininet.log import lg
|
||||
from mininet.util import irange
|
||||
from mininet.log import lg, info
|
||||
from mininet.util import irange, quietRun
|
||||
from mininet.link import TCLink
|
||||
from functools import partial
|
||||
|
||||
import sys
|
||||
flush = sys.stdout.flush
|
||||
@@ -70,41 +73,54 @@ def linearBandwidthTest( lengths ):
|
||||
switches = { 'reference user': UserSwitch,
|
||||
'Open vSwitch kernel': OVSKernelSwitch }
|
||||
|
||||
# UserSwitch is horribly slow with recent kernels.
|
||||
# We can reinstate it once its performance is fixed
|
||||
del switches[ 'reference user' ]
|
||||
|
||||
topo = LinearTestTopo( hostCount )
|
||||
|
||||
# Select TCP Reno
|
||||
output = quietRun( 'sysctl -w net.ipv4.tcp_congestion_control=reno' )
|
||||
assert 'reno' in output
|
||||
|
||||
for datapath in switches.keys():
|
||||
print "*** testing", datapath, "datapath"
|
||||
info( "*** testing", datapath, "datapath\n" )
|
||||
Switch = switches[ datapath ]
|
||||
results[ datapath ] = []
|
||||
net = Mininet( topo=topo, switch=Switch )
|
||||
link = partial( TCLink, delay='2ms', bw=10 )
|
||||
net = Mininet( topo=topo, switch=Switch,
|
||||
controller=Controller, waitConnected=True,
|
||||
link=link )
|
||||
net.start()
|
||||
print "*** testing basic connectivity"
|
||||
info( "*** testing basic connectivity\n" )
|
||||
for n in lengths:
|
||||
net.ping( [ net.hosts[ 0 ], net.hosts[ n ] ] )
|
||||
print "*** testing bandwidth"
|
||||
info( "*** testing bandwidth\n" )
|
||||
for n in lengths:
|
||||
src, dst = net.hosts[ 0 ], net.hosts[ n ]
|
||||
print "testing", src.name, "<->", dst.name,
|
||||
bandwidth = net.iperf( [ src, dst ] )
|
||||
print bandwidth
|
||||
# Try to prime the pump to reduce PACKET_INs during test
|
||||
# since the reference controller is reactive
|
||||
src.cmd( 'telnet', dst.IP(), '5001' )
|
||||
info( "testing", src.name, "<->", dst.name, '\n' )
|
||||
# serverbw = received; _clientbw = buffered
|
||||
serverbw, _clientbw = net.iperf( [ src, dst ], seconds=10 )
|
||||
info( serverbw, '\n' )
|
||||
flush()
|
||||
results[ datapath ] += [ ( n, bandwidth ) ]
|
||||
results[ datapath ] += [ ( n, serverbw ) ]
|
||||
net.stop()
|
||||
|
||||
for datapath in switches.keys():
|
||||
print
|
||||
print "*** Linear network results for", datapath, "datapath:"
|
||||
print
|
||||
info( "\n*** Linear network results for", datapath, "datapath:\n" )
|
||||
result = results[ datapath ]
|
||||
print "SwitchCount\tiperf Results"
|
||||
for switchCount, bandwidth in result:
|
||||
print switchCount, '\t\t',
|
||||
print bandwidth[ 0 ], 'server, ', bandwidth[ 1 ], 'client'
|
||||
print
|
||||
print
|
||||
info( "SwitchCount\tiperf Results\n" )
|
||||
for switchCount, serverbw in result:
|
||||
info( switchCount, '\t\t' )
|
||||
info( serverbw, '\n' )
|
||||
info( '\n')
|
||||
info( '\n' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
lg.setLogLevel( 'info' )
|
||||
sizes = [ 1, 10, 20, 40, 60, 80, 100 ]
|
||||
print "*** Running linearBandwidthTest", sizes
|
||||
sizes = [ 1, 10, 20, 40, 60, 80 ]
|
||||
info( "*** Running linearBandwidthTest", sizes, '\n' )
|
||||
linearBandwidthTest( sizes )
|
||||
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
linuxrouter.py: Example network with Linux IP router
|
||||
|
||||
This example converts a Node into a router using IP forwarding
|
||||
already built into Linux.
|
||||
|
||||
The example topology creates a router and three IP subnets:
|
||||
|
||||
- 192.168.1.0/24 (r0-eth1, IP: 192.168.1.1)
|
||||
- 172.16.0.0/12 (r0-eth2, IP: 172.16.0.1)
|
||||
- 10.0.0.0/8 (r0-eth3, IP: 10.0.0.1)
|
||||
|
||||
Each subnet consists of a single host connected to
|
||||
a single switch:
|
||||
|
||||
r0-eth1 - s1-eth1 - h1-eth0 (IP: 192.168.1.100)
|
||||
r0-eth2 - s2-eth1 - h2-eth0 (IP: 172.16.0.100)
|
||||
r0-eth3 - s3-eth1 - h3-eth0 (IP: 10.0.0.100)
|
||||
|
||||
The example relies on default routing entries that are
|
||||
automatically created for each router interface, as well
|
||||
as 'defaultRoute' parameters for the host interfaces.
|
||||
|
||||
Additional routes may be added to the router or hosts by
|
||||
executing 'ip route' or 'route' commands on the router or hosts.
|
||||
"""
|
||||
|
||||
|
||||
from mininet.topo import Topo
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.cli import CLI
|
||||
|
||||
|
||||
class LinuxRouter( Node ):
|
||||
"A Node with IP forwarding enabled."
|
||||
|
||||
def config( self, **params ):
|
||||
super( LinuxRouter, self).config( **params )
|
||||
# Enable forwarding on the router
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=1' )
|
||||
|
||||
def terminate( self ):
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
|
||||
super( LinuxRouter, self ).terminate()
|
||||
|
||||
|
||||
class NetworkTopo( Topo ):
|
||||
"A LinuxRouter connecting three IP subnets"
|
||||
|
||||
def build( self, **_opts ):
|
||||
|
||||
defaultIP = '192.168.1.1/24' # IP address for r0-eth1
|
||||
router = self.addNode( 'r0', cls=LinuxRouter, ip=defaultIP )
|
||||
|
||||
s1, s2, s3 = [ self.addSwitch( s ) for s in ( 's1', 's2', 's3' ) ]
|
||||
|
||||
self.addLink( s1, router, intfName2='r0-eth1',
|
||||
params2={ 'ip' : defaultIP } ) # for clarity
|
||||
self.addLink( s2, router, intfName2='r0-eth2',
|
||||
params2={ 'ip' : '172.16.0.1/12' } )
|
||||
self.addLink( s3, router, intfName2='r0-eth3',
|
||||
params2={ 'ip' : '10.0.0.1/8' } )
|
||||
|
||||
h1 = self.addHost( 'h1', ip='192.168.1.100/24',
|
||||
defaultRoute='via 192.168.1.1' )
|
||||
h2 = self.addHost( 'h2', ip='172.16.0.100/12',
|
||||
defaultRoute='via 172.16.0.1' )
|
||||
h3 = self.addHost( 'h3', ip='10.0.0.100/8',
|
||||
defaultRoute='via 10.0.0.1' )
|
||||
|
||||
for h, s in [ (h1, s1), (h2, s2), (h3, s3) ]:
|
||||
self.addLink( h, s )
|
||||
|
||||
|
||||
def run():
|
||||
"Test linux router"
|
||||
topo = NetworkTopo()
|
||||
net = Mininet( topo=topo ) # controller is used by s1-s3
|
||||
net.start()
|
||||
info( '*** Routing Table on Router:\n' )
|
||||
info( net[ 'r0' ].cmd( 'route' ) )
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
run()
|
||||
+2981
-85
File diff suppressed because it is too large
Load Diff
Executable
+136
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Simple example of Mobility with Mininet
|
||||
(aka enough rope to hang yourself.)
|
||||
|
||||
We move a host from s1 to s2, s2 to s3, and then back to s1.
|
||||
|
||||
Gotchas:
|
||||
|
||||
The reference controller doesn't support mobility, so we need to
|
||||
manually flush the switch flow tables!
|
||||
|
||||
Good luck!
|
||||
|
||||
to-do:
|
||||
|
||||
- think about wifi/hub behavior
|
||||
- think about clearing last hop - why doesn't that work?
|
||||
"""
|
||||
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import OVSSwitch
|
||||
from mininet.topo import LinearTopo
|
||||
from mininet.log import info, output, warn, setLogLevel
|
||||
|
||||
from random import randint
|
||||
|
||||
|
||||
class MobilitySwitch( OVSSwitch ):
|
||||
"Switch that can reattach and rename interfaces"
|
||||
|
||||
def delIntf( self, intf ):
|
||||
"Remove (and detach) an interface"
|
||||
port = self.ports[ intf ]
|
||||
del self.ports[ intf ]
|
||||
del self.intfs[ port ]
|
||||
del self.nameToIntf[ intf.name ]
|
||||
|
||||
def addIntf( self, intf, rename=False, **kwargs ):
|
||||
"Add (and reparent) an interface"
|
||||
OVSSwitch.addIntf( self, intf, **kwargs )
|
||||
intf.node = self
|
||||
if rename:
|
||||
self.renameIntf( intf )
|
||||
|
||||
def attach( self, intf ):
|
||||
"Attach an interface and set its port"
|
||||
port = self.ports[ intf ]
|
||||
if port:
|
||||
if self.isOldOVS():
|
||||
self.cmd( 'ovs-vsctl add-port', self, intf )
|
||||
else:
|
||||
self.cmd( 'ovs-vsctl add-port', self, intf,
|
||||
'-- set Interface', intf,
|
||||
'ofport_request=%s' % port )
|
||||
self.validatePort( intf )
|
||||
|
||||
def validatePort( self, intf ):
|
||||
"Validate intf's OF port number"
|
||||
ofport = int( self.cmd( 'ovs-vsctl get Interface', intf,
|
||||
'ofport' ) )
|
||||
if ofport != self.ports[ intf ]:
|
||||
warn( 'WARNING: ofport for', intf, 'is actually', ofport,
|
||||
'\n' )
|
||||
|
||||
def renameIntf( self, intf, newname='' ):
|
||||
"Rename an interface (to its canonical name)"
|
||||
intf.ifconfig( 'down' )
|
||||
if not newname:
|
||||
newname = '%s-eth%d' % ( self.name, self.ports[ intf ] )
|
||||
intf.cmd( 'ip link set', intf, 'name', newname )
|
||||
del self.nameToIntf[ intf.name ]
|
||||
intf.name = newname
|
||||
self.nameToIntf[ intf.name ] = intf
|
||||
intf.ifconfig( 'up' )
|
||||
|
||||
def moveIntf( self, intf, switch, port=None, rename=True ):
|
||||
"Move one of our interfaces to another switch"
|
||||
self.detach( intf )
|
||||
self.delIntf( intf )
|
||||
switch.addIntf( intf, port=port, rename=rename )
|
||||
switch.attach( intf )
|
||||
|
||||
|
||||
def printConnections( switches ):
|
||||
"Compactly print connected nodes to each switch"
|
||||
for sw in switches:
|
||||
output( '%s: ' % sw )
|
||||
for intf in sw.intfList():
|
||||
link = intf.link
|
||||
if link:
|
||||
intf1, intf2 = link.intf1, link.intf2
|
||||
remote = intf1 if intf1.node != sw else intf2
|
||||
output( '%s(%s) ' % ( remote.node, sw.ports[ intf ] ) )
|
||||
output( '\n' )
|
||||
|
||||
|
||||
def moveHost( host, oldSwitch, newSwitch, newPort=None ):
|
||||
"Move a host from old switch to new switch"
|
||||
hintf, sintf = host.connectionsTo( oldSwitch )[ 0 ]
|
||||
oldSwitch.moveIntf( sintf, newSwitch, port=newPort )
|
||||
return hintf, sintf
|
||||
|
||||
|
||||
def mobilityTest():
|
||||
"A simple test of mobility"
|
||||
info( '* Simple mobility test\n' )
|
||||
net = Mininet( topo=LinearTopo( 3 ), switch=MobilitySwitch )
|
||||
info( '* Starting network:\n' )
|
||||
net.start()
|
||||
printConnections( net.switches )
|
||||
info( '* Testing network\n' )
|
||||
net.pingAll()
|
||||
info( '* Identifying switch interface for h1\n' )
|
||||
h1, old = net.get( 'h1', 's1' )
|
||||
for s in 2, 3, 1:
|
||||
new = net[ 's%d' % s ]
|
||||
port = randint( 10, 20 )
|
||||
info( '* Moving', h1, 'from', old, 'to', new, 'port', port, '\n' )
|
||||
hintf, sintf = moveHost( h1, old, new, newPort=port )
|
||||
info( '*', hintf, 'is now connected to', sintf, '\n' )
|
||||
info( '* Clearing out old flows\n' )
|
||||
for sw in net.switches:
|
||||
sw.dpctl( 'del-flows' )
|
||||
info( '* New network:\n' )
|
||||
printConnections( net.switches )
|
||||
info( '* Testing connectivity:\n' )
|
||||
net.pingAll()
|
||||
old = new
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
mobilityTest()
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
This is a simple example that demonstrates multiple links
|
||||
between nodes.
|
||||
"""
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.net import Mininet
|
||||
from mininet.topo import Topo
|
||||
|
||||
def runMultiLink():
|
||||
"Create and run multiple link network"
|
||||
topo = simpleMultiLinkTopo( n=2 )
|
||||
net = Mininet( topo=topo )
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
class simpleMultiLinkTopo( Topo ):
|
||||
"Simple topology with multiple links"
|
||||
|
||||
def __init__( self, n, **kwargs ):
|
||||
Topo.__init__( self, **kwargs )
|
||||
|
||||
h1, h2 = self.addHost( 'h1' ), self.addHost( 'h2' )
|
||||
s1 = self.addSwitch( 's1' )
|
||||
|
||||
for _ in range( n ):
|
||||
self.addLink( s1, h1 )
|
||||
self.addLink( s1, h2 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
runMultiLink()
|
||||
+8
-10
@@ -8,10 +8,11 @@ multiple hosts and monitor their output interactively for a period=
|
||||
of time.
|
||||
"""
|
||||
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.log import info, setLogLevel
|
||||
|
||||
from select import poll, POLLIN
|
||||
from time import time
|
||||
@@ -23,13 +24,8 @@ def chunks( l, n ):
|
||||
def startpings( host, targetips ):
|
||||
"Tell host to repeatedly ping targets"
|
||||
|
||||
targetips.append( '10.0.0.200' )
|
||||
|
||||
targetips = ' '.join( targetips )
|
||||
|
||||
# BL: Not sure why loopback intf isn't up!
|
||||
host.cmd( 'ifconfig lo up' )
|
||||
|
||||
# Simple ping loop
|
||||
cmd = ( 'while true; do '
|
||||
' for ip in %s; do ' % targetips +
|
||||
@@ -39,8 +35,8 @@ def startpings( host, targetips ):
|
||||
' done; '
|
||||
'done &' )
|
||||
|
||||
print ( '*** Host %s (%s) will be pinging ips: %s' %
|
||||
( host.name, host.IP(), targetips ) )
|
||||
info( '*** Host %s (%s) will be pinging ips: %s\n' %
|
||||
( host.name, host.IP(), targetips ) )
|
||||
|
||||
host.cmd( cmd )
|
||||
|
||||
@@ -49,7 +45,7 @@ def multiping( netsize, chunksize, seconds):
|
||||
|
||||
# Create network and identify subnets
|
||||
topo = SingleSwitchTopo( netsize )
|
||||
net = Mininet( topo=topo )
|
||||
net = Mininet( topo=topo, waitConnected=True )
|
||||
net.start()
|
||||
hosts = net.hosts
|
||||
subnets = chunks( hosts, chunksize )
|
||||
@@ -63,6 +59,8 @@ def multiping( netsize, chunksize, seconds):
|
||||
# Start pings
|
||||
for subnet in subnets:
|
||||
ips = [ host.IP() for host in subnet ]
|
||||
#adding bogus to generate packet loss
|
||||
ips.append( '10.0.0.200' )
|
||||
for host in subnet:
|
||||
startpings( host, ips )
|
||||
|
||||
@@ -72,7 +70,7 @@ def multiping( netsize, chunksize, seconds):
|
||||
readable = poller.poll(1000)
|
||||
for fd, _mask in readable:
|
||||
node = Node.outToNode[ fd ]
|
||||
print '%s:' % node.name, node.monitor().strip()
|
||||
info( '%s:' % node.name, node.monitor().strip(), '\n' )
|
||||
|
||||
# Stop pings
|
||||
for host in hosts:
|
||||
|
||||
@@ -5,19 +5,22 @@ Simple example of sending output to multiple files and
|
||||
monitoring them
|
||||
"""
|
||||
|
||||
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.net import Mininet
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.log import info, setLogLevel
|
||||
from mininet.util import decode
|
||||
|
||||
from time import time
|
||||
from select import poll, POLLIN
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
|
||||
def monitorFiles( outfiles, seconds, timeoutms ):
|
||||
"Monitor set of files and return [(host, line)...]"
|
||||
devnull = open( '/dev/null', 'w' )
|
||||
tails, fdToFile, fdToHost = {}, {}, {}
|
||||
for h, outfile in outfiles.iteritems():
|
||||
for h, outfile in outfiles.items():
|
||||
tail = Popen( [ 'tail', '-f', outfile ],
|
||||
stdout=PIPE, stderr=devnull )
|
||||
fd = tail.stdout.fileno()
|
||||
@@ -38,7 +41,7 @@ def monitorFiles( outfiles, seconds, timeoutms ):
|
||||
host = fdToHost[ fd ]
|
||||
# Wait for a line of output
|
||||
line = f.readline().strip()
|
||||
yield host, line
|
||||
yield host, decode( line )
|
||||
else:
|
||||
# If we timed out, return nothing
|
||||
yield None, ''
|
||||
@@ -53,7 +56,7 @@ def monitorTest( N=3, seconds=3 ):
|
||||
net = Mininet( topo )
|
||||
net.start()
|
||||
hosts = net.hosts
|
||||
print "Starting test..."
|
||||
info( "Starting test...\n" )
|
||||
server = hosts[ 0 ]
|
||||
outfiles, errfiles = {}, {}
|
||||
for h in hosts:
|
||||
@@ -67,10 +70,10 @@ def monitorTest( N=3, seconds=3 ):
|
||||
'>', outfiles[ h ],
|
||||
'2>', errfiles[ h ],
|
||||
'&' )
|
||||
print "Monitoring output for", seconds, "seconds"
|
||||
info( "Monitoring output for", seconds, "seconds\n" )
|
||||
for h, line in monitorFiles( outfiles, seconds, timeoutms=500 ):
|
||||
if h:
|
||||
print '%s: %s' % ( h.name, line )
|
||||
info( '%s: %s\n' % ( h.name, line ) )
|
||||
for h in hosts:
|
||||
h.cmd('kill %ping')
|
||||
net.stop()
|
||||
|
||||
@@ -22,7 +22,7 @@ if __name__ == '__main__':
|
||||
info( "*** Initializing Mininet and kernel modules\n" )
|
||||
OVSKernelSwitch.setup()
|
||||
info( "*** Creating network\n" )
|
||||
network = Mininet( TreeTopo( depth=2, fanout=2 ), switch=OVSKernelSwitch)
|
||||
network = Mininet( TreeTopo( depth=2, fanout=2 ), switch=OVSKernelSwitch )
|
||||
info( "*** Starting network\n" )
|
||||
network.start()
|
||||
info( "*** Running ping test\n" )
|
||||
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Example to create a Mininet topology and connect it to the internet via NAT
|
||||
"""
|
||||
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import lg, info
|
||||
from mininet.topolib import TreeNet
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
lg.setLogLevel( 'info')
|
||||
net = TreeNet( depth=1, fanout=4 )
|
||||
# Add NAT connectivity
|
||||
net.addNAT().configDefault()
|
||||
net.start()
|
||||
info( "*** Hosts are running and should have internet connectivity\n" )
|
||||
info( "*** Type 'exit' or control-D to shut down network\n" )
|
||||
CLI( net )
|
||||
# Shut down NAT
|
||||
net.stop()
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
natnet.py: Example network with NATs
|
||||
|
||||
|
||||
h0
|
||||
|
|
||||
s0
|
||||
|
|
||||
----------------
|
||||
| |
|
||||
nat1 nat2
|
||||
| |
|
||||
s1 s2
|
||||
| |
|
||||
h1 h2
|
||||
|
||||
"""
|
||||
|
||||
from mininet.topo import Topo
|
||||
from mininet.net import Mininet
|
||||
from mininet.nodelib import NAT
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.cli import CLI
|
||||
from mininet.util import irange
|
||||
|
||||
class InternetTopo(Topo):
|
||||
"Single switch connected to n hosts."
|
||||
def __init__(self, n=2, **opts):
|
||||
Topo.__init__(self, **opts)
|
||||
|
||||
# set up inet switch
|
||||
inetSwitch = self.addSwitch('s0')
|
||||
# add inet host
|
||||
inetHost = self.addHost('h0')
|
||||
self.addLink(inetSwitch, inetHost)
|
||||
|
||||
# add local nets
|
||||
for i in irange(1, n):
|
||||
inetIntf = 'nat%d-eth0' % i
|
||||
localIntf = 'nat%d-eth1' % i
|
||||
localIP = '192.168.%d.1' % i
|
||||
localSubnet = '192.168.%d.0/24' % i
|
||||
natParams = { 'ip' : '%s/24' % localIP }
|
||||
# add NAT to topology
|
||||
nat = self.addNode('nat%d' % i, cls=NAT, subnet=localSubnet,
|
||||
inetIntf=inetIntf, localIntf=localIntf)
|
||||
switch = self.addSwitch('s%d' % i)
|
||||
# connect NAT to inet and local switches
|
||||
self.addLink(nat, inetSwitch, intfName1=inetIntf)
|
||||
self.addLink(nat, switch, intfName1=localIntf, params1=natParams)
|
||||
# add host and connect to local switch
|
||||
host = self.addHost('h%d' % i,
|
||||
ip='192.168.%d.100/24' % i,
|
||||
defaultRoute='via %s' % localIP)
|
||||
self.addLink(host, switch)
|
||||
|
||||
def run():
|
||||
"Create network and run the CLI"
|
||||
topo = InternetTopo()
|
||||
net = Mininet(topo=topo)
|
||||
net.start()
|
||||
CLI(net)
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
run()
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
Create a network with 5 hosts, numbered 1-4 and 9.
|
||||
Validate that the port numbers match to the interface name,
|
||||
and that the ovs ports match the mininet ports.
|
||||
"""
|
||||
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Controller
|
||||
from mininet.log import setLogLevel, info, warn
|
||||
|
||||
def validatePort( switch, intf ):
|
||||
"Validate intf's OF port number"
|
||||
ofport = int( switch.cmd( 'ovs-vsctl get Interface', intf,
|
||||
'ofport' ) )
|
||||
if ofport != switch.ports[ intf ]:
|
||||
warn( 'WARNING: ofport for', intf, 'is actually', ofport, '\n' )
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
def testPortNumbering():
|
||||
|
||||
"""Test port numbering:
|
||||
Create a network with 5 hosts (using Mininet's
|
||||
mid-level API) and check that implicit and
|
||||
explicit port numbering works as expected."""
|
||||
|
||||
net = Mininet( controller=Controller )
|
||||
|
||||
info( '*** Adding controller\n' )
|
||||
net.addController( 'c0' )
|
||||
|
||||
info( '*** Adding hosts\n' )
|
||||
h1 = net.addHost( 'h1', ip='10.0.0.1' )
|
||||
h2 = net.addHost( 'h2', ip='10.0.0.2' )
|
||||
h3 = net.addHost( 'h3', ip='10.0.0.3' )
|
||||
h4 = net.addHost( 'h4', ip='10.0.0.4' )
|
||||
h5 = net.addHost( 'h5', ip='10.0.0.5' )
|
||||
|
||||
info( '*** Adding switch\n' )
|
||||
s1 = net.addSwitch( 's1' )
|
||||
|
||||
info( '*** Creating links\n' )
|
||||
# host 1-4 connect to ports 1-4 on the switch
|
||||
net.addLink( h1, s1 )
|
||||
net.addLink( h2, s1 )
|
||||
net.addLink( h3, s1 )
|
||||
net.addLink( h4, s1 )
|
||||
# specify a different port to connect host 5 to on the switch.
|
||||
net.addLink( h5, s1, port1=1, port2= 9)
|
||||
|
||||
info( '*** Starting network\n' )
|
||||
net.start()
|
||||
|
||||
# print the interfaces and their port numbers
|
||||
info( '\n*** printing and validating the ports '
|
||||
'running on each interface\n' )
|
||||
for intfs in s1.intfList():
|
||||
if not intfs.name == "lo":
|
||||
info( intfs, ': ', s1.ports[intfs],
|
||||
'\n' )
|
||||
info( 'Validating that', intfs,
|
||||
'is actually on port', s1.ports[intfs], '... ' )
|
||||
if validatePort( s1, intfs ):
|
||||
info( 'Validated.\n' )
|
||||
info( '\n' )
|
||||
|
||||
# test the network with pingall
|
||||
net.pingAll()
|
||||
info( '\n' )
|
||||
|
||||
info( '*** Stopping network\n' )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
testPortNumbering()
|
||||
+3
-2
@@ -5,10 +5,11 @@ This example monitors a number of hosts using host.popen() and
|
||||
pmonitor()
|
||||
"""
|
||||
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.util import custom, pmonitor
|
||||
|
||||
def monitorhosts( hosts=5, sched='cfs' ):
|
||||
@@ -27,7 +28,7 @@ def monitorhosts( hosts=5, sched='cfs' ):
|
||||
# Monitor them and print output
|
||||
for host, line in pmonitor( popens ):
|
||||
if host:
|
||||
print "<%s>: %s" % ( host.name, line.strip() )
|
||||
info( "<%s>: %s" % ( host.name, line ) )
|
||||
# Done
|
||||
net.stop()
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
from mininet.net import Mininet
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.util import pmonitor
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
from time import time
|
||||
from signal import SIGINT
|
||||
|
||||
@@ -14,20 +16,21 @@ def pmonitorTest( N=3, seconds=10 ):
|
||||
net = Mininet( topo )
|
||||
net.start()
|
||||
hosts = net.hosts
|
||||
print "Starting test..."
|
||||
info( "Starting test...\n" )
|
||||
server = hosts[ 0 ]
|
||||
popens = {}
|
||||
for h in hosts:
|
||||
popens[ h ] = h.popen('ping', server.IP() )
|
||||
print "Monitoring output for", seconds, "seconds"
|
||||
info( "Monitoring output for", seconds, "seconds\n" )
|
||||
endTime = time() + seconds
|
||||
for h, line in pmonitor( popens, timeoutms=500 ):
|
||||
if h:
|
||||
print '%s: %s' % ( h.name, line ),
|
||||
info( '<%s>: %s' % ( h.name, line ) )
|
||||
if time() >= endTime:
|
||||
for p in popens.values():
|
||||
p.send_signal( SIGINT )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
pmonitorTest()
|
||||
|
||||
@@ -8,6 +8,7 @@ but it exposes the configuration details and allows customization.
|
||||
For most tasks, the higher-level API will be preferable.
|
||||
"""
|
||||
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Node
|
||||
from mininet.link import Link
|
||||
@@ -40,7 +41,7 @@ def scratchNet( cname='controller', cargs='-v ptcp:' ):
|
||||
switch.cmd( 'ovs-vsctl del-br dp0' )
|
||||
switch.cmd( 'ovs-vsctl add-br dp0' )
|
||||
for intf in switch.intfs.values():
|
||||
print switch.cmd( 'ovs-vsctl add-port dp0 %s' % intf )
|
||||
switch.cmd( 'ovs-vsctl add-port dp0 %s\n' % intf )
|
||||
|
||||
# Note: controller and switch are in root namespace, and we
|
||||
# can connect via loopback interface
|
||||
|
||||
@@ -52,7 +52,7 @@ def scratchNetUser( cname='controller', cargs='ptcp:' ):
|
||||
info( '*** Starting controller and user datapath\n' )
|
||||
controller.cmd( cname + ' ' + cargs + '&' )
|
||||
switch.cmd( 'ifconfig lo 127.0.0.1' )
|
||||
intfs = [ str( i ) for i in sintf1, sintf2 ]
|
||||
intfs = str( sintf1 ), str( sintf2 )
|
||||
switch.cmd( 'ofdatapath -i ' + ','.join( intfs ) + ' ptcp: &' )
|
||||
switch.cmd( 'ofprotocol tcp:' + controller.IP() + ' tcp:localhost &' )
|
||||
|
||||
|
||||
+29
-18
@@ -9,41 +9,52 @@ iperf will hang indefinitely if the TCP handshake fails
|
||||
to complete.
|
||||
"""
|
||||
|
||||
|
||||
from mininet.topo import Topo
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.link import TCLink
|
||||
from mininet.util import dumpNodeConnections
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.log import setLogLevel, info
|
||||
|
||||
class SingleSwitchTopo(Topo):
|
||||
from sys import argv
|
||||
|
||||
# It would be nice if we didn't have to do this:
|
||||
# pylint: disable=arguments-differ
|
||||
|
||||
class SingleSwitchTopo( Topo ):
|
||||
"Single switch connected to n hosts."
|
||||
def __init__(self, n=2, **opts):
|
||||
Topo.__init__(self, **opts)
|
||||
def build( self, n=2, lossy=True ):
|
||||
switch = self.addSwitch('s1')
|
||||
for h in range(n):
|
||||
# Each host gets 50%/n of system CPU
|
||||
host = self.addHost('h%s' % (h + 1),
|
||||
cpu=.5 / n)
|
||||
# 10 Mbps, 5ms delay, 10% loss
|
||||
self.addLink(host, switch,
|
||||
bw=10, delay='5ms', loss=10, use_htb=True)
|
||||
if lossy:
|
||||
# 10 Mbps, 5ms delay, 10% packet loss
|
||||
self.addLink(host, switch,
|
||||
bw=10, delay='5ms', loss=10, use_htb=True)
|
||||
else:
|
||||
# 10 Mbps, 5ms delay, no packet loss
|
||||
self.addLink(host, switch,
|
||||
bw=10, delay='5ms', loss=0, use_htb=True)
|
||||
|
||||
def perfTest():
|
||||
|
||||
def perfTest( lossy=True ):
|
||||
"Create network and run simple performance test"
|
||||
topo = SingleSwitchTopo(n=4)
|
||||
net = Mininet(topo=topo,
|
||||
host=CPULimitedHost, link=TCLink)
|
||||
topo = SingleSwitchTopo( n=4, lossy=lossy )
|
||||
net = Mininet( topo=topo,
|
||||
host=CPULimitedHost, link=TCLink,
|
||||
autoStaticArp=True )
|
||||
net.start()
|
||||
print "Dumping host connections"
|
||||
info( "Dumping host connections\n" )
|
||||
dumpNodeConnections(net.hosts)
|
||||
print "Testing network connectivity"
|
||||
net.pingAll()
|
||||
print "Testing bandwidth between h1 and h4"
|
||||
info( "Testing bandwidth between h1 and h4\n" )
|
||||
h1, h4 = net.getNodeByName('h1', 'h4')
|
||||
net.iperf((h1, h4))
|
||||
net.iperf( ( h1, h4 ), l4Type='UDP' )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel('info')
|
||||
perfTest()
|
||||
setLogLevel( 'info' )
|
||||
# Prevent test_simpleperf from failing due to packet loss
|
||||
perfTest( lossy=( 'testmode' not in argv ) )
|
||||
|
||||
+33
-21
@@ -16,50 +16,58 @@ demonstrates:
|
||||
- running server processes (sshd in this case) on hosts
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import lg
|
||||
from mininet.node import Node, OVSKernelSwitch
|
||||
from mininet.log import lg, info
|
||||
from mininet.node import Node
|
||||
from mininet.topolib import TreeTopo
|
||||
from mininet.link import Link
|
||||
from mininet.util import waitListening
|
||||
|
||||
|
||||
def TreeNet( depth=1, fanout=2, **kwargs ):
|
||||
"Convenience function for creating tree networks."
|
||||
topo = TreeTopo( depth, fanout )
|
||||
return Mininet( topo, **kwargs )
|
||||
|
||||
def connectToRootNS( network, switch, ip, prefixLen, routes ):
|
||||
def connectToRootNS( network, switch, ip, routes ):
|
||||
"""Connect hosts to root namespace via switch. Starts network.
|
||||
network: Mininet() network object
|
||||
switch: switch to connect to root namespace
|
||||
ip: IP address for root namespace node
|
||||
prefixLen: IP address prefix length (e.g. 8, 16, 24)
|
||||
routes: host networks to route to"""
|
||||
# Create a node in root namespace and link to switch 0
|
||||
root = Node( 'root', inNamespace=False )
|
||||
intf = Link( root, switch ).intf1
|
||||
root.setIP( ip, prefixLen, intf )
|
||||
intf = network.addLink( root, switch ).intf1
|
||||
root.setIP( ip, intf=intf )
|
||||
# Start network that now includes link to root namespace
|
||||
network.start()
|
||||
# Add routes from root ns to hosts
|
||||
for route in routes:
|
||||
root.cmd( 'route add -net ' + route + ' dev ' + str( intf ) )
|
||||
|
||||
def sshd( network, cmd='/usr/sbin/sshd', opts='-D' ):
|
||||
"Start a network, connect it to root ns, and run sshd on all hosts."
|
||||
switch = network.switches[ 0 ] # switch to use
|
||||
ip = '10.123.123.1' # our IP address on host network
|
||||
routes = [ '10.0.0.0/8' ] # host networks to route to
|
||||
connectToRootNS( network, switch, ip, 8, routes )
|
||||
def sshd( network, cmd='/usr/sbin/sshd', opts='-D',
|
||||
ip='10.123.123.1/32', routes=None, switch=None ):
|
||||
"""Start a network, connect it to root ns, and run sshd on all hosts.
|
||||
ip: root-eth0 IP address in root namespace (10.123.123.1/32)
|
||||
routes: Mininet host networks to route to (10.0/24)
|
||||
switch: Mininet switch to connect to root namespace (s1)"""
|
||||
if not switch:
|
||||
switch = network[ 's1' ] # switch to use
|
||||
if not routes:
|
||||
routes = [ '10.0.0.0/24' ]
|
||||
connectToRootNS( network, switch, ip, routes )
|
||||
for host in network.hosts:
|
||||
host.cmd( cmd + ' ' + opts + '&' )
|
||||
print
|
||||
print "*** Hosts are running sshd at the following addresses:"
|
||||
print
|
||||
info( "*** Waiting for ssh daemons to start\n" )
|
||||
for server in network.hosts:
|
||||
waitListening( server=server, port=22, timeout=5 )
|
||||
|
||||
info( "\n*** Hosts are running sshd at the following addresses:\n" )
|
||||
for host in network.hosts:
|
||||
print host.name, host.IP()
|
||||
print
|
||||
print "*** Type 'exit' or control-D to shut down network"
|
||||
info( host.name, host.IP(), '\n' )
|
||||
info( "\n*** Type 'exit' or control-D to shut down network\n" )
|
||||
CLI( network )
|
||||
for host in network.hosts:
|
||||
host.cmd( 'kill %' + cmd )
|
||||
@@ -67,5 +75,9 @@ def sshd( network, cmd='/usr/sbin/sshd', opts='-D' ):
|
||||
|
||||
if __name__ == '__main__':
|
||||
lg.setLogLevel( 'info')
|
||||
net = TreeNet( depth=1, fanout=4, switch=OVSKernelSwitch )
|
||||
sshd( net )
|
||||
net = TreeNet( depth=1, fanout=4 )
|
||||
# get sshd args from the command line or use default args
|
||||
# useDNS=no -u0 to avoid reverse DNS lookup timeout
|
||||
argvopts = ' '.join( sys.argv[ 1: ] ) if len( sys.argv ) > 1 else (
|
||||
'-D -o UseDNS=no -u0' )
|
||||
sshd( net, opts=argvopts )
|
||||
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Run all mininet.examples tests
|
||||
-v : verbose output
|
||||
-quick : skip tests that take more than ~30 seconds
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import sys
|
||||
from mininet.util import ensureRoot
|
||||
from mininet.clean import cleanup
|
||||
|
||||
class MininetTestResult( unittest.TextTestResult ):
|
||||
def addFailure( self, test, err ):
|
||||
super( MininetTestResult, self ).addFailure( test, err )
|
||||
cleanup()
|
||||
def addError( self,test, err ):
|
||||
super( MininetTestResult, self ).addError( test, err )
|
||||
cleanup()
|
||||
|
||||
class MininetTestRunner( unittest.TextTestRunner ):
|
||||
def _makeResult( self ):
|
||||
return MininetTestResult( self.stream, self.descriptions, self.verbosity )
|
||||
|
||||
def runTests( testDir, verbosity=1 ):
|
||||
"discover and run all tests in testDir"
|
||||
# ensure root and cleanup before starting tests
|
||||
ensureRoot()
|
||||
cleanup()
|
||||
# discover all tests in testDir
|
||||
testSuite = unittest.defaultTestLoader.discover( testDir )
|
||||
# run tests
|
||||
success = MininetTestRunner( verbosity=verbosity ).run( testSuite ).wasSuccessful()
|
||||
sys.exit( 0 if success else 1 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
# get the directory containing example tests
|
||||
testDir = os.path.dirname( os.path.realpath( __file__ ) )
|
||||
verbosity = 2 if '-v' in sys.argv else 1
|
||||
runTests( testDir, verbosity )
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Tests for baresshd.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
from mininet.clean import cleanup, sh
|
||||
from sys import stdout
|
||||
|
||||
class testBareSSHD( unittest.TestCase ):
|
||||
|
||||
opts = [ 'Welcome to h1', pexpect.EOF, pexpect.TIMEOUT ]
|
||||
|
||||
def connected( self ):
|
||||
"Log into ssh server, check banner, then exit"
|
||||
p = pexpect.spawn( 'ssh 10.0.0.1 -o ConnectTimeout=1 '
|
||||
'-o StrictHostKeyChecking=no '
|
||||
'-i /tmp/ssh/test_rsa exit' )
|
||||
while True:
|
||||
index = p.expect( self.opts )
|
||||
if index == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def setUp( self ):
|
||||
# verify that sshd is not running
|
||||
self.assertFalse( self.connected() )
|
||||
# create public key pair for testing
|
||||
sh( 'rm -rf /tmp/ssh' )
|
||||
sh( 'mkdir /tmp/ssh' )
|
||||
sh( "ssh-keygen -t rsa -P '' -f /tmp/ssh/test_rsa" )
|
||||
sh( 'cat /tmp/ssh/test_rsa.pub >> /tmp/ssh/authorized_keys' )
|
||||
# run example with custom sshd args
|
||||
cmd = ( 'python -m mininet.examples.baresshd '
|
||||
'-o AuthorizedKeysFile=/tmp/ssh/authorized_keys '
|
||||
'-o StrictModes=no' )
|
||||
p = pexpect.spawn( cmd )
|
||||
runOpts = [ 'You may now ssh into h1 at 10.0.0.1',
|
||||
'after 5 seconds, h1 is not listening on port 22',
|
||||
pexpect.EOF, pexpect.TIMEOUT ]
|
||||
while True:
|
||||
index = p.expect( runOpts )
|
||||
if index == 0:
|
||||
break
|
||||
else:
|
||||
self.tearDown()
|
||||
self.fail( 'sshd failed to start in host h1' )
|
||||
|
||||
def testSSH( self ):
|
||||
"Simple test to verify that we can ssh into h1"
|
||||
result = False
|
||||
# try to connect up to 3 times; sshd can take a while to start
|
||||
result = self.connected()
|
||||
self.assertTrue( result )
|
||||
|
||||
def tearDown( self ):
|
||||
# kill the ssh process
|
||||
sh( "ps aux | grep ssh |grep Banner| awk '{ print $2 }' | xargs kill" )
|
||||
cleanup()
|
||||
# remove public key pair
|
||||
sh( 'rm -rf /tmp/ssh' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Tests for bind.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testBind( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
def setUp( self ):
|
||||
self.net = pexpect.spawn( 'python -m mininet.examples.bind' )
|
||||
self.net.expect( "Private Directories: \[([\w\s,'/]+)\]" )
|
||||
self.directories = []
|
||||
# parse directories from mn output
|
||||
for d in self.net.match.group(1).split(', '):
|
||||
self.directories.append( d.strip("'") )
|
||||
self.net.expect( self.prompt )
|
||||
self.assertTrue( len( self.directories ) > 0 )
|
||||
|
||||
def testCreateFile( self ):
|
||||
"Create a file, a.txt, in the first private directory and verify"
|
||||
fileName = 'a.txt'
|
||||
directory = self.directories[ 0 ]
|
||||
path = directory + '/' + fileName
|
||||
self.net.sendline( 'h1 touch %s; ls %s' % ( path, directory ) )
|
||||
index = self.net.expect( [ fileName, self.prompt ] )
|
||||
self.assertTrue( index == 0 )
|
||||
self.net.expect( self.prompt )
|
||||
self.net.sendline( 'h1 rm %s' % path )
|
||||
self.net.expect( self.prompt )
|
||||
|
||||
def testIsolation( self ):
|
||||
"Create a file in two hosts and verify that contents are different"
|
||||
fileName = 'b.txt'
|
||||
directory = self.directories[ 0 ]
|
||||
path = directory + '/' + fileName
|
||||
contents = { 'h1' : '1', 'h2' : '2' }
|
||||
# Verify file doesn't exist, then write private copy of file
|
||||
for host in contents:
|
||||
value = contents[ host ]
|
||||
self.net.sendline( '%s cat %s' % ( host, path ) )
|
||||
self.net.expect( 'No such file' )
|
||||
self.net.expect( self.prompt )
|
||||
self.net.sendline( '%s echo %s > %s' % ( host, value, path ) )
|
||||
self.net.expect( self.prompt )
|
||||
# Verify file contents
|
||||
for host in contents:
|
||||
value = contents[ host ]
|
||||
self.net.sendline( '%s cat %s' % ( host, path ) )
|
||||
self.net.expect( value )
|
||||
self.net.expect( self.prompt )
|
||||
self.net.sendline( '%s rm %s' % ( host, path ) )
|
||||
self.net.expect( self.prompt )
|
||||
|
||||
# TODO: need more tests
|
||||
|
||||
def tearDown( self ):
|
||||
self.net.sendline( 'exit' )
|
||||
self.net.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
A simple sanity check test for cluster edition
|
||||
'''
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
|
||||
class clusterSanityCheck( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
def testClusterPingAll( self ):
|
||||
p = pexpect.spawn( 'python -m mininet.examples.clusterSanity' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'pingall' )
|
||||
p.expect ( '(\d+)% dropped' )
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
self.assertEqual( percent, 0 )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Tests for controllers.py and controllers2.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testControllers( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
def connectedTest( self, name, cmap ):
|
||||
"Verify that switches are connected to the controller specified by cmap"
|
||||
p = pexpect.spawn( 'python -m %s' % name )
|
||||
p.expect( self.prompt )
|
||||
# but first a simple ping test
|
||||
p.sendline( 'pingall' )
|
||||
p.expect ( '(\d+)% dropped' )
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
self.assertEqual( percent, 0 )
|
||||
p.expect( self.prompt )
|
||||
# verify connected controller
|
||||
for switch in cmap:
|
||||
p.sendline( 'sh ovs-vsctl get-controller %s' % switch )
|
||||
p.expect( 'tcp:([\d.:]+)')
|
||||
actual = p.match.group(1)
|
||||
expected = cmap[ switch ]
|
||||
self.assertEqual( actual, expected )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
def testControllers( self ):
|
||||
c0 = '127.0.0.1:6633'
|
||||
c1 = '127.0.0.1:6634'
|
||||
cmap = { 's1': c0, 's2': c1, 's3': c0 }
|
||||
self.connectedTest( 'mininet.examples.controllers', cmap )
|
||||
|
||||
def testControllers2( self ):
|
||||
c0 = '127.0.0.1:6633'
|
||||
c1 = '127.0.0.1:6634'
|
||||
cmap = { 's1': c0, 's2': c1 }
|
||||
self.connectedTest( 'mininet.examples.controllers2', cmap )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for controlnet.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testControlNet( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
def testPingall( self ):
|
||||
"Simple pingall test that verifies 0% packet drop in data network"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.controlnet' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'pingall' )
|
||||
p.expect ( '(\d+)% dropped' )
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
self.assertEqual( percent, 0 )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
def testFailover( self ):
|
||||
"Kill controllers and verify that switch, s1, fails over properly"
|
||||
count = 1
|
||||
p = pexpect.spawn( 'python -m mininet.examples.controlnet' )
|
||||
p.expect( self.prompt )
|
||||
lp = pexpect.spawn( 'tail -f /tmp/s1-ofp.log' )
|
||||
lp.expect( 'tcp:\d+\.\d+\.\d+\.(\d+):\d+: connected' )
|
||||
ip = int( lp.match.group( 1 ) )
|
||||
self.assertEqual( count, ip )
|
||||
count += 1
|
||||
for c in [ 'c0', 'c1' ]:
|
||||
p.sendline( '%s ifconfig %s-eth0 down' % ( c, c) )
|
||||
p.expect( self.prompt )
|
||||
lp.expect( 'tcp:\d+\.\d+\.\d+\.(\d+):\d+: connected' )
|
||||
ip = int( lp.match.group( 1 ) )
|
||||
self.assertEqual( count, ip )
|
||||
count += 1
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for cpu.py
|
||||
|
||||
results format:
|
||||
|
||||
sched cpu received bits/sec
|
||||
cfs 50% 8.14e+09
|
||||
cfs 40% 6.48e+09
|
||||
cfs 30% 4.56e+09
|
||||
cfs 20% 2.84e+09
|
||||
cfs 10% 1.29e+09
|
||||
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testCPU( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
@unittest.skipIf( '-quick' in sys.argv, 'long test' )
|
||||
def testCPU( self ):
|
||||
"Verify that CPU utilization is monotonically decreasing for each scheduler"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.cpu', timeout=300 )
|
||||
# matches each line from results( shown above )
|
||||
opts = [ '([a-z]+)\t([\d\.]+)%\t([\d\.e\+]+)',
|
||||
pexpect.EOF ]
|
||||
scheds = []
|
||||
while True:
|
||||
index = p.expect( opts )
|
||||
if index == 0:
|
||||
sched = p.match.group( 1 )
|
||||
cpu = float( p.match.group( 2 ) )
|
||||
bw = float( p.match.group( 3 ) )
|
||||
if sched not in scheds:
|
||||
scheds.append( sched )
|
||||
else:
|
||||
self.assertTrue( bw < previous_bw,
|
||||
"%e should be less than %e\n" %
|
||||
( bw, previous_bw ) )
|
||||
previous_bw = bw
|
||||
else:
|
||||
break
|
||||
|
||||
self.assertTrue( len( scheds ) > 0 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for emptynet.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testEmptyNet( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
def testEmptyNet( self ):
|
||||
"Run simple CLI tests: pingall (verify 0% drop) and iperf (sanity)"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.emptynet' )
|
||||
p.expect( self.prompt )
|
||||
# pingall test
|
||||
p.sendline( 'pingall' )
|
||||
p.expect ( '(\d+)% dropped' )
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
self.assertEqual( percent, 0 )
|
||||
p.expect( self.prompt )
|
||||
# iperf test
|
||||
p.sendline( 'iperf' )
|
||||
p.expect( "Results: \['[\d.]+ .bits/sec', '[\d.]+ .bits/sec'\]" )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for hwintf.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import re
|
||||
|
||||
from mininet.util import pexpect
|
||||
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.node import Node
|
||||
from mininet.link import Link
|
||||
|
||||
|
||||
class testHwintf( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
def setUp( self ):
|
||||
self.h3 = Node( 't0', ip='10.0.0.3/8' )
|
||||
self.n0 = Node( 't1', inNamespace=False )
|
||||
Link( self.h3, self.n0 )
|
||||
self.h3.configDefault()
|
||||
|
||||
def testLocalPing( self ):
|
||||
"Verify connectivity between virtual hosts using pingall"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.hwintf %s' % self.n0.intf() )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'pingall' )
|
||||
p.expect ( '(\d+)% dropped' )
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
self.assertEqual( percent, 0 )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
def testExternalPing( self ):
|
||||
"Verify connnectivity between virtual host and virtual-physical 'external' host "
|
||||
p = pexpect.spawn( 'python -m mininet.examples.hwintf %s' % self.n0.intf() )
|
||||
p.expect( self.prompt )
|
||||
# test ping external to internal
|
||||
expectStr = '(\d+) packets transmitted, (\d+) received'
|
||||
m = re.search( expectStr, self.h3.cmd( 'ping -v -c 1 10.0.0.1' ) )
|
||||
tx = m.group( 1 )
|
||||
rx = m.group( 2 )
|
||||
self.assertEqual( tx, rx )
|
||||
# test ping internal to external
|
||||
p.sendline( 'h1 ping -c 1 10.0.0.3')
|
||||
p.expect( expectStr )
|
||||
tx = p.match.group( 1 )
|
||||
rx = p.match.group( 2 )
|
||||
self.assertEqual( tx, rx )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
def tearDown( self ):
|
||||
self.h3.stop( deleteIntfs=True )
|
||||
self.n0.stop( deleteIntfs=True )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'warning' )
|
||||
unittest.main()
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for intfOptions.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testIntfOptions( unittest.TestCase ):
|
||||
|
||||
def testIntfOptions( self ):
|
||||
"verify that intf.config is correctly limiting traffic"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.intfoptions ' )
|
||||
tolerance = .2 # plus or minus 20%
|
||||
opts = [ "Results: \['([\d\.]+) .bits/sec",
|
||||
"Results: \['10M', '([\d\.]+) .bits/sec",
|
||||
"h(\d+)->h(\d+): (\d)/(\d),"
|
||||
"rtt min/avg/max/mdev ([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+) ms",
|
||||
pexpect.EOF ]
|
||||
while True:
|
||||
index = p.expect( opts, timeout=600 )
|
||||
if index == 0:
|
||||
BW = 5
|
||||
bw = float( p.match.group( 1 ) )
|
||||
self.assertGreaterEqual( bw, BW * ( 1 - tolerance ) )
|
||||
self.assertLessEqual( bw, BW * ( 1 + tolerance ) )
|
||||
elif index == 1:
|
||||
BW = 10
|
||||
measuredBw = float( p.match.group( 1 ) )
|
||||
loss = ( measuredBw / BW ) * 100
|
||||
self.assertGreaterEqual( loss, 50 * ( 1 - tolerance ) )
|
||||
self.assertLessEqual( loss, 50 * ( 1 + tolerance ) )
|
||||
elif index == 2:
|
||||
delay = float( p.match.group( 6 ) )
|
||||
self.assertGreaterEqual( delay, 15 * ( 1 - tolerance ) )
|
||||
self.assertLessEqual( delay, 15 * ( 1 + tolerance ) )
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for limit.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testLimit( unittest.TestCase ):
|
||||
|
||||
@unittest.skipIf( '-quick' in sys.argv, 'long test' )
|
||||
def testLimit( self ):
|
||||
"Verify that CPU limits are within a 2% tolerance of limit for each scheduler"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.limit' )
|
||||
opts = [ '\*\*\* Testing network ([\d\.]+) Mbps',
|
||||
'\*\*\* Results: \[([\d\., ]+)\]',
|
||||
pexpect.EOF ]
|
||||
count = 0
|
||||
bw = 0
|
||||
tolerance = 2
|
||||
while True:
|
||||
index = p.expect( opts )
|
||||
if index == 0:
|
||||
bw = float( p.match.group( 1 ) )
|
||||
count += 1
|
||||
elif index == 1:
|
||||
results = p.match.group( 1 )
|
||||
for x in results.split( ',' ):
|
||||
result = float( x )
|
||||
self.assertTrue( result < bw + tolerance )
|
||||
self.assertTrue( result > bw - tolerance )
|
||||
else:
|
||||
break
|
||||
|
||||
self.assertTrue( count > 0 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for linearbandwidth.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testLinearBandwidth( unittest.TestCase ):
|
||||
|
||||
@unittest.skipIf( '-quick' in sys.argv, 'long test' )
|
||||
def testLinearBandwidth( self ):
|
||||
"Verify that bandwidth is monotonically decreasing as # of hops increases"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.linearbandwidth' )
|
||||
count = 0
|
||||
opts = [ '\*\*\* Linear network results',
|
||||
'(\d+)\s+([\d\.]+) (.bits)',
|
||||
pexpect.EOF ]
|
||||
while True:
|
||||
index = p.expect( opts, timeout=600 )
|
||||
if index == 0:
|
||||
count += 1
|
||||
elif index == 1:
|
||||
n = int( p.match.group( 1 ) )
|
||||
bw = float( p.match.group( 2 ) )
|
||||
unit = p.match.group( 3 )
|
||||
if unit[ 0 ] == 'K':
|
||||
bw *= 10 ** 3
|
||||
elif unit[ 0 ] == 'M':
|
||||
bw *= 10 ** 6
|
||||
elif unit[ 0 ] == 'G':
|
||||
bw *= 10 ** 9
|
||||
# check that we have a previous result to compare to
|
||||
if n != 1:
|
||||
info = ( 'bw: %.2e bits/s across %d switches, '
|
||||
'previous: %.2e bits/s across %d switches' %
|
||||
( bw, n, previous_bw, previous_n ) )
|
||||
self.assertTrue( bw < previous_bw, info )
|
||||
previous_bw, previous_n = bw, n
|
||||
else:
|
||||
break
|
||||
|
||||
# verify that we received results from at least one switch
|
||||
self.assertTrue( count > 0 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for linuxrouter.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
from mininet.util import quietRun
|
||||
|
||||
class testLinuxRouter( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
def testPingall( self ):
|
||||
"Test connectivity between hosts"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.linuxrouter' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'pingall' )
|
||||
p.expect ( '(\d+)% dropped' )
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
self.assertEqual( percent, 0 )
|
||||
|
||||
def testRouterPing( self ):
|
||||
"Test connectivity from h1 to router"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.linuxrouter' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 ping -c 1 r0' )
|
||||
p.expect ( '(\d+)% packet loss' )
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
self.assertEqual( percent, 0 )
|
||||
|
||||
def testTTL( self ):
|
||||
"Verify that the TTL is decremented"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.linuxrouter' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 ping -c 1 h2' )
|
||||
p.expect ( 'ttl=(\d+)' )
|
||||
ttl = int( p.match.group( 1 ) ) if p.match else -1
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
self.assertEqual( ttl, 63 ) # 64 - 1
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for mobility.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from subprocess import check_output
|
||||
|
||||
class testMobility( unittest.TestCase ):
|
||||
|
||||
def testMobility( self ):
|
||||
"Run the example and verify its 4 ping results"
|
||||
cmd = 'python -m mininet.examples.mobility 2>&1'
|
||||
grep = ' | grep -c " 0% dropped" '
|
||||
result = check_output( cmd + grep, shell=True )
|
||||
assert int( result ) == 4
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Test for multiple links between nodes
|
||||
validates mininet interfaces against systems interfaces
|
||||
'''
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testMultiLink( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
def testMultiLink(self):
|
||||
p = pexpect.spawn( 'python -m mininet.examples.multilink' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'intfs' )
|
||||
p.expect( 's(\d): lo' )
|
||||
intfsOutput = p.before
|
||||
# parse interfaces from mininet intfs, and store them in a list
|
||||
hostToIntfs = intfsOutput.split( '\r\n' )[ 1:3 ]
|
||||
intfList = []
|
||||
for hostToIntf in hostToIntfs:
|
||||
intfList += [ intf for intf in
|
||||
hostToIntf.split()[1].split(',') ]
|
||||
|
||||
# get interfaces from system by running ifconfig on every host
|
||||
sysIntfList = []
|
||||
opts = [ 'h(\d)-eth(\d)', self.prompt ]
|
||||
p.expect( self.prompt )
|
||||
|
||||
p.sendline( 'h1 ifconfig' )
|
||||
while True:
|
||||
p.expect( opts )
|
||||
if p.after == self.prompt:
|
||||
break
|
||||
sysIntfList.append( p.after )
|
||||
|
||||
p.sendline( 'h2 ifconfig' )
|
||||
while True:
|
||||
p.expect( opts )
|
||||
if p.after == self.prompt:
|
||||
break
|
||||
sysIntfList.append( p.after )
|
||||
|
||||
failMsg = ( 'The systems interfaces and mininet interfaces\n'
|
||||
'are not the same' )
|
||||
|
||||
self.assertEqual( sysIntfList, intfList, msg=failMsg )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for multiping.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
from collections import defaultdict
|
||||
|
||||
class testMultiPing( unittest.TestCase ):
|
||||
|
||||
def testMultiPing( self ):
|
||||
"""Verify that each target is pinged at least once, and
|
||||
that pings to 'real' targets are successful and unknown targets fail"""
|
||||
p = pexpect.spawn( 'python -m mininet.examples.multiping' )
|
||||
opts = [ "Host (h\d+) \(([\d.]+)\) will be pinging ips: ([\d\. ]+)",
|
||||
"(h\d+): ([\d.]+) -> ([\d.]+) \d packets transmitted, (\d) received",
|
||||
pexpect.EOF ]
|
||||
pings = defaultdict( list )
|
||||
while True:
|
||||
index = p.expect( opts )
|
||||
if index == 0:
|
||||
name = p.match.group(1)
|
||||
ip = p.match.group(2)
|
||||
targets = p.match.group(3).split()
|
||||
pings[ name ] += targets
|
||||
elif index == 1:
|
||||
name = p.match.group(1)
|
||||
ip = p.match.group(2)
|
||||
target = p.match.group(3)
|
||||
received = int( p.match.group(4) )
|
||||
if target == '10.0.0.200':
|
||||
self.assertEqual( received, 0, p.match.group(0) + '\n' +
|
||||
target + ' received %d != 0 packets' % received )
|
||||
else:
|
||||
self.assertEqual( received, 1, p.match.group(0) + '\n' +
|
||||
target + ' received %d != 1 packets' % received )
|
||||
try:
|
||||
pings[ name ].remove( target )
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
self.assertTrue( len( pings ) > 0, 'too few pings' )
|
||||
for t in pings.values():
|
||||
self.assertEqual( len( t ), 0, 'missed ping target(s): %s' % t )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for multipoll.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testMultiPoll( unittest.TestCase ):
|
||||
|
||||
def testMultiPoll( self ):
|
||||
"Verify that we receive one ping per second per host"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.multipoll' )
|
||||
opts = [ "\*\*\* (h\d) :" ,
|
||||
"(h\d+): \d+ bytes from",
|
||||
"Monitoring output for (\d+) seconds",
|
||||
pexpect.EOF ]
|
||||
pings, seconds = {}, -1
|
||||
while True:
|
||||
index = p.expect( opts )
|
||||
if index == 0:
|
||||
name = p.match.group( 1 )
|
||||
pings[ name ] = 0
|
||||
elif index == 1:
|
||||
name = p.match.group( 1 )
|
||||
pings[ name ] += 1
|
||||
elif index == 2:
|
||||
seconds = int( p.match.group( 1 ) )
|
||||
else:
|
||||
break
|
||||
self.assertTrue( len( pings ) > 0 )
|
||||
# make sure we have received at least one ping per second
|
||||
for count in pings.values():
|
||||
self.assertTrue( count >= seconds,
|
||||
'%d pings < %d seconds' % ( count, seconds ) )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for multitest.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testMultiTest( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
def testMultiTest( self ):
|
||||
"Verify pingall (0% dropped) and hX-eth0 interface for each host (ifconfig)"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.multitest' )
|
||||
p.expect( '(\d+)% dropped' )
|
||||
dropped = int( p.match.group( 1 ) )
|
||||
self.assertEqual( dropped, 0 )
|
||||
ifCount = 0
|
||||
while True:
|
||||
index = p.expect( [ 'h\d-eth0', self.prompt ] )
|
||||
if index == 0:
|
||||
ifCount += 1
|
||||
elif index == 1:
|
||||
p.sendline( 'exit' )
|
||||
break
|
||||
p.wait()
|
||||
self.assertEqual( ifCount, 4 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for nat.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
from mininet.util import quietRun
|
||||
|
||||
destIP = '8.8.8.8' # Google DNS
|
||||
|
||||
class testNAT( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
@unittest.skipIf( '0 received' in quietRun( 'ping -c 1 %s' % destIP ),
|
||||
'Destination IP is not reachable' )
|
||||
def testNAT( self ):
|
||||
"Attempt to ping an IP on the Internet and verify 0% packet loss"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.nat' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 ping -c 1 %s' % destIP )
|
||||
p.expect ( '(\d+)% packet loss' )
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
self.assertEqual( percent, 0 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for natnet.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
from mininet.util import quietRun
|
||||
|
||||
class testNATNet( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
def setUp( self ):
|
||||
self.net = pexpect.spawn( 'python -m mininet.examples.natnet' )
|
||||
self.net.expect( self.prompt )
|
||||
|
||||
def testPublicPing( self ):
|
||||
"Attempt to ping the public server (h0) from h1 and h2"
|
||||
self.net.sendline( 'h1 ping -c 1 h0' )
|
||||
self.net.expect ( '(\d+)% packet loss' )
|
||||
percent = int( self.net.match.group( 1 ) ) if self.net.match else -1
|
||||
self.assertEqual( percent, 0 )
|
||||
self.net.expect( self.prompt )
|
||||
|
||||
self.net.sendline( 'h2 ping -c 1 h0' )
|
||||
self.net.expect ( '(\d+)% packet loss' )
|
||||
percent = int( self.net.match.group( 1 ) ) if self.net.match else -1
|
||||
self.assertEqual( percent, 0 )
|
||||
self.net.expect( self.prompt )
|
||||
|
||||
def testPrivatePing( self ):
|
||||
"Attempt to ping h1 and h2 from public server"
|
||||
self.net.sendline( 'h0 ping -c 1 -t 1 h1' )
|
||||
result = self.net.expect ( [ 'unreachable', 'loss' ] )
|
||||
self.assertEqual( result, 0 )
|
||||
self.net.expect( self.prompt )
|
||||
|
||||
self.net.sendline( 'h0 ping -c 1 -t 1 h2' )
|
||||
result = self.net.expect ( [ 'unreachable', 'loss' ] )
|
||||
self.assertEqual( result, 0 )
|
||||
self.net.expect( self.prompt )
|
||||
|
||||
def testPrivateToPrivatePing( self ):
|
||||
"Attempt to ping from NAT'ed host h1 to NAT'ed host h2"
|
||||
self.net.sendline( 'h1 ping -c 1 -t 1 h2' )
|
||||
result = self.net.expect ( [ '[Uu]nreachable', 'loss' ] )
|
||||
self.assertEqual( result, 0 )
|
||||
self.net.expect( self.prompt )
|
||||
|
||||
def tearDown( self ):
|
||||
self.net.sendline( 'exit' )
|
||||
self.net.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for numberedports.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
from collections import defaultdict
|
||||
from mininet.node import OVSSwitch
|
||||
|
||||
class testNumberedports( unittest.TestCase ):
|
||||
|
||||
@unittest.skipIf( OVSSwitch.setup() or OVSSwitch.isOldOVS(), "old version of OVS" )
|
||||
def testConsistency( self ):
|
||||
"""verify consistency between mininet and ovs ports"""
|
||||
p = pexpect.spawn( 'python -m mininet.examples.numberedports' )
|
||||
opts = [ 'Validating that s1-eth\d is actually on port \d ... Validated.',
|
||||
'Validating that s1-eth\d is actually on port \d ... WARNING',
|
||||
pexpect.EOF ]
|
||||
correct_ports = True
|
||||
count = 0
|
||||
while True:
|
||||
index = p.expect( opts )
|
||||
if index == 0:
|
||||
count += 1
|
||||
elif index == 1:
|
||||
correct_ports = False
|
||||
elif index == 2:
|
||||
self.assertNotEqual( 0, count )
|
||||
break
|
||||
self.assertTrue( correct_ports )
|
||||
|
||||
def testNumbering( self ):
|
||||
"""verify that all of the port numbers are printed correctly and consistent with their interface"""
|
||||
p = pexpect.spawn( 'python -m mininet.examples.numberedports' )
|
||||
opts = [ 's1-eth(\d+) : (\d+)',
|
||||
pexpect.EOF ]
|
||||
count_intfs = 0
|
||||
while True:
|
||||
index = p.expect( opts )
|
||||
if index == 0:
|
||||
count_intfs += 1
|
||||
intfport = p.match.group( 1 )
|
||||
ofport = p.match.group( 2 )
|
||||
self.assertEqual( intfport, ofport )
|
||||
elif index == 1:
|
||||
break
|
||||
self.assertNotEqual( 0, count_intfs )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for popen.py and popenpoll.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testPopen( unittest.TestCase ):
|
||||
|
||||
def pingTest( self, name ):
|
||||
"Verify that there are no dropped packets for each host"
|
||||
p = pexpect.spawn( 'python -m %s' % name )
|
||||
opts = [ "<(h\d+)>: PING ",
|
||||
"<(h\d+)>: (\d+) packets transmitted, (\d+) received",
|
||||
pexpect.EOF ]
|
||||
pings = {}
|
||||
while True:
|
||||
index = p.expect( opts )
|
||||
if index == 0:
|
||||
name = p.match.group(1)
|
||||
pings[ name ] = 0
|
||||
elif index == 1:
|
||||
name = p.match.group(1)
|
||||
transmitted = p.match.group(2)
|
||||
received = p.match.group(3)
|
||||
# verify no dropped packets
|
||||
self.assertEqual( received, transmitted )
|
||||
pings[ name ] += 1
|
||||
else:
|
||||
break
|
||||
self.assertTrue( len(pings) > 0 )
|
||||
# verify that each host has gotten results
|
||||
for count in pings.values():
|
||||
self.assertEqual( count, 1 )
|
||||
|
||||
def testPopen( self ):
|
||||
self.pingTest( 'mininet.examples.popen' )
|
||||
|
||||
def testPopenPoll( self ):
|
||||
self.pingTest( 'mininet.examples.popenpoll' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for scratchnet.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
|
||||
class testScratchNet( unittest.TestCase ):
|
||||
|
||||
opts = [ "1 packets transmitted, 1 received, 0% packet loss", pexpect.EOF ]
|
||||
|
||||
def pingTest( self, name ):
|
||||
"Verify that no ping packets were dropped"
|
||||
p = pexpect.spawn( 'python -m %s' % name )
|
||||
index = p.expect( self.opts, timeout=120 )
|
||||
self.assertEqual( index, 0 )
|
||||
|
||||
def testPingKernel( self ):
|
||||
self.pingTest( 'mininet.examples.scratchnet' )
|
||||
|
||||
def testPingUser( self ):
|
||||
self.pingTest( 'mininet.examples.scratchnetuser' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for simpleperf.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
from mininet.examples.simpleperf import SingleSwitchTopo
|
||||
|
||||
class testSimplePerf( unittest.TestCase ):
|
||||
|
||||
@unittest.skipIf( '-quick' in sys.argv, 'long test' )
|
||||
def testE2E( self ):
|
||||
"Run the example and verify iperf results"
|
||||
# 10 Mb/s, plus or minus 20% tolerance
|
||||
BW = 10
|
||||
TOLERANCE = .2
|
||||
p = pexpect.spawn( 'python -m mininet.examples.simpleperf testmode' )
|
||||
# check iperf results
|
||||
p.expect( "Results: \['10M', '([\d\.]+) .bits/sec", timeout=480 )
|
||||
measuredBw = float( p.match.group( 1 ) )
|
||||
lowerBound = BW * ( 1 - TOLERANCE )
|
||||
upperBound = BW + ( 1 + TOLERANCE )
|
||||
self.assertGreaterEqual( measuredBw, lowerBound )
|
||||
self.assertLessEqual( measuredBw, upperBound )
|
||||
p.wait()
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'warning' )
|
||||
unittest.main()
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for sshd.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
from mininet.clean import sh
|
||||
|
||||
class testSSHD( unittest.TestCase ):
|
||||
|
||||
opts = [ '\(yes/no\)\?', 'refused', 'Welcome|\$|#', pexpect.EOF, pexpect.TIMEOUT ]
|
||||
|
||||
def connected( self, ip ):
|
||||
"Log into ssh server, check banner, then exit"
|
||||
# Note: this test will fail if "Welcome" is not in the sshd banner
|
||||
# and '#'' or '$'' are not in the prompt
|
||||
p = pexpect.spawn( 'ssh -i /tmp/ssh/test_rsa %s' % ip, timeout=10 )
|
||||
while True:
|
||||
index = p.expect( self.opts )
|
||||
if index == 0:
|
||||
print( p.match.group(0) )
|
||||
p.sendline( 'yes' )
|
||||
elif index == 1:
|
||||
return False
|
||||
elif index == 2:
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def setUp( self ):
|
||||
# create public key pair for testing
|
||||
sh( 'rm -rf /tmp/ssh' )
|
||||
sh( 'mkdir /tmp/ssh' )
|
||||
sh( "ssh-keygen -t rsa -P '' -f /tmp/ssh/test_rsa" )
|
||||
sh( 'cat /tmp/ssh/test_rsa.pub >> /tmp/ssh/authorized_keys' )
|
||||
cmd = ( 'python -m mininet.examples.sshd -D '
|
||||
'-o AuthorizedKeysFile=/tmp/ssh/authorized_keys '
|
||||
'-o StrictModes=no -o UseDNS=no -u0' )
|
||||
# run example with custom sshd args
|
||||
self.net = pexpect.spawn( cmd )
|
||||
self.net.expect( 'mininet>' )
|
||||
|
||||
def testSSH( self ):
|
||||
"Verify that we can ssh into all hosts (h1 to h4)"
|
||||
for h in range( 1, 5 ):
|
||||
self.assertTrue( self.connected( '10.0.0.%d' % h ) )
|
||||
|
||||
def tearDown( self ):
|
||||
self.net.sendline( 'exit' )
|
||||
self.net.wait()
|
||||
# remove public key pair
|
||||
sh( 'rm -rf /tmp/ssh' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for tree1024.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testTree1024( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
@unittest.skipIf( '-quick' in sys.argv, 'long test' )
|
||||
def testTree1024( self ):
|
||||
"Run the example and do a simple ping test from h1 to h1024"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.tree1024' )
|
||||
p.expect( self.prompt, timeout=6000 ) # it takes awhile to set up
|
||||
p.sendline( 'h1 ping -c 20 h1024' )
|
||||
p.expect ( '(\d+)% packet loss' )
|
||||
packetLossPercent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
# Tolerate slow startup on some systems - we should revisit this
|
||||
# and determine the root cause.
|
||||
self.assertLess( packetLossPercent, 60 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for treeping64.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
|
||||
class testTreePing64( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
@unittest.skipIf( '-quick' in sys.argv, 'long test' )
|
||||
def testTreePing64( self ):
|
||||
"Run the example and verify ping results"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.treeping64' )
|
||||
p.expect( 'Tree network ping results:', timeout=6000 )
|
||||
count = 0
|
||||
while True:
|
||||
index = p.expect( [ '(\d+)% packet loss', pexpect.EOF ] )
|
||||
if index == 0:
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
self.assertEqual( percent, 0 )
|
||||
count += 1
|
||||
else:
|
||||
break
|
||||
self.assertTrue( count > 0 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Test for vlanhost.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from mininet.util import pexpect
|
||||
import sys
|
||||
from mininet.util import quietRun
|
||||
|
||||
class testVLANHost( unittest.TestCase ):
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
@unittest.skipIf( '-quick' in sys.argv, 'long test' )
|
||||
def testVLANTopo( self ):
|
||||
"Test connectivity (or lack thereof) between hosts in VLANTopo"
|
||||
p = pexpect.spawn( 'python -m mininet.examples.vlanhost' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'pingall 1' ) #ping timeout=1
|
||||
p.expect( '(\d+)% dropped', timeout=30 ) # there should be 24 failed pings
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
self.assertEqual( percent, 80 )
|
||||
|
||||
def testSpecificVLAN( self ):
|
||||
"Test connectivity between hosts on a specific VLAN"
|
||||
vlan = 1001
|
||||
p = pexpect.spawn( 'python -m mininet.examples.vlanhost %d' % vlan )
|
||||
p.expect( self.prompt )
|
||||
|
||||
p.sendline( 'h1 ping -c 1 h2' )
|
||||
p.expect ( '(\d+)% packet loss' )
|
||||
percent = int( p.match.group( 1 ) ) if p.match else -1
|
||||
p.expect( self.prompt )
|
||||
|
||||
p.sendline( 'h1 ifconfig' )
|
||||
i = p.expect( ['h1-eth0.%d' % vlan, pexpect.TIMEOUT ], timeout=2 )
|
||||
p.expect( self.prompt )
|
||||
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
self.assertEqual( percent, 0 ) # no packet loss on ping
|
||||
self.assertEqual( i, 0 ) # check vlan intf is present
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -9,10 +9,10 @@ and running sysctl -p. Check util/sysctl_addon.
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.node import OVSKernelSwitch
|
||||
from mininet.node import OVSSwitch
|
||||
from mininet.topolib import TreeNet
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
network = TreeNet( depth=2, fanout=32, switch=OVSKernelSwitch )
|
||||
network = TreeNet( depth=2, fanout=32, switch=OVSSwitch )
|
||||
network.run( CLI, network )
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
"Create a 64-node tree network, and test connectivity using ping."
|
||||
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
from mininet.log import setLogLevel, info
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch # , KernelSwitch
|
||||
from mininet.topolib import TreeNet
|
||||
|
||||
@@ -15,17 +16,16 @@ def treePing64():
|
||||
'Open vSwitch kernel': OVSKernelSwitch }
|
||||
|
||||
for name in switches:
|
||||
print "*** Testing", name, "datapath"
|
||||
info( "*** Testing", name, "datapath\n" )
|
||||
switch = switches[ name ]
|
||||
network = TreeNet( depth=2, fanout=8, switch=switch )
|
||||
result = network.run( network.pingAll )
|
||||
results[ name ] = result
|
||||
|
||||
print
|
||||
print "*** Tree network ping results:"
|
||||
info( "\n*** Tree network ping results:\n" )
|
||||
for name in switches:
|
||||
print "%s: %d%% packet loss" % ( name, results[ name ] )
|
||||
print
|
||||
info( "%s: %d%% packet loss\n" % ( name, results[ name ] ) )
|
||||
info( '\n' )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'info' )
|
||||
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
vlanhost.py: Host subclass that uses a VLAN tag for the default interface.
|
||||
|
||||
Dependencies:
|
||||
This class depends on the "vlan" package
|
||||
$ sudo apt-get install vlan
|
||||
|
||||
Usage (example uses VLAN ID=1000):
|
||||
From the command line:
|
||||
sudo mn --custom vlanhost.py --host vlan,vlan=1000
|
||||
|
||||
From a script (see exampleUsage function below):
|
||||
from functools import partial
|
||||
from vlanhost import VLANHost
|
||||
|
||||
....
|
||||
|
||||
host = partial( VLANHost, vlan=1000 )
|
||||
net = Mininet( host=host, ... )
|
||||
|
||||
Directly running this script:
|
||||
sudo python vlanhost.py 1000
|
||||
|
||||
"""
|
||||
|
||||
from mininet.node import Host
|
||||
from mininet.topo import Topo
|
||||
from mininet.util import quietRun
|
||||
from mininet.log import error
|
||||
|
||||
class VLANHost( Host ):
|
||||
"Host connected to VLAN interface"
|
||||
|
||||
def config( self, vlan=100, **params ):
|
||||
"""Configure VLANHost according to (optional) parameters:
|
||||
vlan: VLAN ID for default interface"""
|
||||
|
||||
r = super( VLANHost, self ).config( **params )
|
||||
|
||||
intf = self.defaultIntf()
|
||||
# remove IP from default, "physical" interface
|
||||
self.cmd( 'ifconfig %s inet 0' % intf )
|
||||
# create VLAN interface
|
||||
self.cmd( 'vconfig add %s %d' % ( intf, vlan ) )
|
||||
# assign the host's IP to the VLAN interface
|
||||
self.cmd( 'ifconfig %s.%d inet %s' % ( intf, vlan, params['ip'] ) )
|
||||
# update the intf name and host's intf map
|
||||
newName = '%s.%d' % ( intf, vlan )
|
||||
# update the (Mininet) interface to refer to VLAN interface name
|
||||
intf.name = newName
|
||||
# add VLAN interface to host's name to intf map
|
||||
self.nameToIntf[ newName ] = intf
|
||||
|
||||
return r
|
||||
|
||||
hosts = { 'vlan': VLANHost }
|
||||
|
||||
|
||||
def exampleAllHosts( vlan ):
|
||||
"""Simple example of how VLANHost can be used in a script"""
|
||||
# This is where the magic happens...
|
||||
host = partial( VLANHost, vlan=vlan )
|
||||
# vlan (type: int): VLAN ID to be used by all hosts
|
||||
|
||||
# Start a basic network using our VLANHost
|
||||
topo = SingleSwitchTopo( k=2 )
|
||||
net = Mininet( host=host, topo=topo )
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
|
||||
class VLANStarTopo( Topo ):
|
||||
"""Example topology that uses host in multiple VLANs
|
||||
|
||||
The topology has a single switch. There are k VLANs with
|
||||
n hosts in each, all connected to the single switch. There
|
||||
are also n hosts that are not in any VLAN, also connected to
|
||||
the switch."""
|
||||
|
||||
def build( self, k=2, n=2, vlanBase=100 ):
|
||||
s1 = self.addSwitch( 's1' )
|
||||
for i in range( k ):
|
||||
vlan = vlanBase + i
|
||||
for j in range(n):
|
||||
name = 'h%d-%d' % ( j+1, vlan )
|
||||
h = self.addHost( name, cls=VLANHost, vlan=vlan )
|
||||
self.addLink( h, s1 )
|
||||
for j in range( n ):
|
||||
h = self.addHost( 'h%d' % (j+1) )
|
||||
self.addLink( h, s1 )
|
||||
|
||||
|
||||
def exampleCustomTags():
|
||||
"""Simple example that exercises VLANStarTopo"""
|
||||
|
||||
net = Mininet( topo=VLANStarTopo() )
|
||||
net.start()
|
||||
CLI( net )
|
||||
net.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.cli import CLI
|
||||
from mininet.topo import SingleSwitchTopo
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
setLogLevel( 'info' )
|
||||
|
||||
if not quietRun( 'which vconfig' ):
|
||||
error( "Cannot find command 'vconfig'\nThe package",
|
||||
"'vlan' is required in Ubuntu or Debian,",
|
||||
"or 'vconfig' in Fedora\n" )
|
||||
exit()
|
||||
|
||||
if len( sys.argv ) >= 2:
|
||||
exampleAllHosts( vlan=int( sys.argv[ 1 ] ) )
|
||||
else:
|
||||
exampleCustomTags()
|
||||
+100
-65
@@ -10,83 +10,118 @@ It may also get rid of 'false positives', but hopefully
|
||||
nothing irreplaceable!
|
||||
"""
|
||||
|
||||
from subprocess import Popen, PIPE, STDOUT, check_output as co
|
||||
from sys import stdout, exit
|
||||
from time import sleep
|
||||
from subprocess import ( Popen, PIPE, check_output as co,
|
||||
CalledProcessError )
|
||||
import time
|
||||
|
||||
from mininet.log import info, error
|
||||
from mininet.log import info
|
||||
from mininet.term import cleanUpScreens
|
||||
from mininet.util import decode
|
||||
|
||||
def sh( cmd ):
|
||||
"Run a command in the shell and return non-empty output lines"
|
||||
"Print a command and send it to the shell"
|
||||
info( cmd + '\n' )
|
||||
output = ( Popen( [ '/bin/sh', '-c', cmd ], stdout=PIPE )
|
||||
.communicate()[ 0 ]
|
||||
.strip()
|
||||
.split( '\n' ) )
|
||||
return [ s for s in output if s ]
|
||||
result = Popen( [ '/bin/sh', '-c', cmd ], stdout=PIPE ).communicate()[ 0 ]
|
||||
return decode( result )
|
||||
|
||||
def cleanup():
|
||||
"""Clean up junk which might be left over from old runs;
|
||||
do fast stuff before slow dp and link removal!"""
|
||||
|
||||
info( "*** Removing excess "
|
||||
"controllers/ofprotocols/ofdatapaths/pings/noxes\n" )
|
||||
zombies = 'controller ofprotocol ofdatapath ping nox_core lt-nox_core '
|
||||
zombies += 'ovs-openflowd ovs-controller udpbwtest mnexec'
|
||||
# Note: real zombie processes can't actually be killed, since they
|
||||
# are already (un)dead. Then again,
|
||||
# you can't connect to them either, so they're mostly harmless.
|
||||
sh( 'killall -9 ' + zombies + ' 2> /dev/null' )
|
||||
|
||||
# And kill off sudo mnexec
|
||||
sh( 'pkill -9 -f "sudo mnexec"')
|
||||
|
||||
info( "*** Removing junk from /tmp\n" )
|
||||
sh( 'rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log' )
|
||||
|
||||
info( "*** Removing old X11 tunnels\n" )
|
||||
cleanUpScreens()
|
||||
|
||||
info( "*** Removing excess kernel datapaths\n" )
|
||||
dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'" )
|
||||
for dp in dps:
|
||||
sh( 'dpctl deldp ' + dp )
|
||||
|
||||
info( "*** Removing OVS datapaths\n" )
|
||||
dps = sh("ovs-vsctl list-br")
|
||||
for dp in dps:
|
||||
sh( 'ovs-vsctl del-br ' + dp )
|
||||
if co( 'ovs-vsctl list-br', shell=True ):
|
||||
raise Excpetion( "Error: could not remove all OVS datapaths" )
|
||||
|
||||
info( "*** Removing all links of the pattern foo-ethX\n" )
|
||||
links = sh( "ip link show | egrep -o '(\w+-eth\w+)'" )
|
||||
for link in links:
|
||||
sh( "ip link del " + link )
|
||||
if sh( "ip link show | egrep -o '(\w+-eth\w+)'" ):
|
||||
raise Exception( "Error could not remove stale links")
|
||||
|
||||
info( "*** Killing stale mininet node processes\n" )
|
||||
sh( 'pkill -9 -f mininet:' )
|
||||
def killprocs( pattern ):
|
||||
"Reliably terminate processes matching a pattern (including args)"
|
||||
sh( 'pkill -9 -f %s' % pattern )
|
||||
# Make sure they are gone
|
||||
while True:
|
||||
try:
|
||||
pids = co( 'pgrep -f mininet:'.split() )
|
||||
except:
|
||||
pids = co( [ 'pgrep', '-f', pattern ] )
|
||||
except CalledProcessError:
|
||||
pids = ''
|
||||
if pids:
|
||||
sh( 'pkill -f 9 mininet:' )
|
||||
sleep( .5 )
|
||||
sh( 'pkill -9 -f %s' % pattern )
|
||||
time.sleep( .5 )
|
||||
else:
|
||||
break
|
||||
|
||||
info( "*** Removing stale namespaces\n" )
|
||||
nses = sh( "ip netns list" )
|
||||
for ns in nses:
|
||||
sh( "ip netns del " + ns )
|
||||
if co( "ip netns list", shell=True ):
|
||||
error( "Error: could not remove all namespaces - exiting\n" )
|
||||
exit( 1 )
|
||||
class Cleanup( object ):
|
||||
"Wrapper for cleanup()"
|
||||
|
||||
info( "*** Cleanup complete.\n" )
|
||||
callbacks = []
|
||||
|
||||
@classmethod
|
||||
def cleanup( cls):
|
||||
"""Clean up junk which might be left over from old runs;
|
||||
do fast stuff before slow dp and link removal!"""
|
||||
|
||||
info( "*** Removing excess controllers/ofprotocols/ofdatapaths/"
|
||||
"pings/noxes\n" )
|
||||
zombies = ( 'controller ofprotocol ofdatapath ping nox_core'
|
||||
'lt-nox_core ovs-openflowd ovs-controller'
|
||||
'ovs-testcontroller udpbwtest mnexec ivs ryu-manager' )
|
||||
# Note: real zombie processes can't actually be killed, since they
|
||||
# are already (un)dead. Then again,
|
||||
# you can't connect to them either, so they're mostly harmless.
|
||||
# Send SIGTERM first to give processes a chance to shutdown cleanly.
|
||||
sh( 'killall ' + zombies + ' 2> /dev/null' )
|
||||
time.sleep( 1 )
|
||||
sh( 'killall -9 ' + zombies + ' 2> /dev/null' )
|
||||
|
||||
# And kill off sudo mnexec
|
||||
sh( 'pkill -9 -f "sudo mnexec"')
|
||||
|
||||
info( "*** Removing junk from /tmp\n" )
|
||||
sh( 'rm -f /tmp/vconn* /tmp/vlogs* /tmp/*.out /tmp/*.log' )
|
||||
|
||||
info( "*** Removing old X11 tunnels\n" )
|
||||
cleanUpScreens()
|
||||
|
||||
info( "*** Removing excess kernel datapaths\n" )
|
||||
dps = sh( "ps ax | egrep -o 'dp[0-9]+' | sed 's/dp/nl:/'"
|
||||
).splitlines()
|
||||
for dp in dps:
|
||||
if dp:
|
||||
sh( 'dpctl deldp ' + dp )
|
||||
info( "*** Removing OVS datapaths\n" )
|
||||
dps = sh("ovs-vsctl --timeout=1 list-br").strip().splitlines()
|
||||
if dps:
|
||||
sh( "ovs-vsctl " + " -- ".join( "--if-exists del-br " + dp
|
||||
for dp in dps if dp ) )
|
||||
# And in case the above didn't work...
|
||||
dps = sh( "ovs-vsctl --timeout=1 list-br" ).strip().splitlines()
|
||||
for dp in dps:
|
||||
sh( 'ovs-vsctl del-br ' + dp )
|
||||
|
||||
info( "*** Removing all links of the pattern foo-ethX\n" )
|
||||
links = sh( "ip link show | "
|
||||
"egrep -o '([-_.[:alnum:]]+-eth[[:digit:]]+)'"
|
||||
).splitlines()
|
||||
# Delete blocks of links
|
||||
n = 1000 # chunk size
|
||||
for i in range( 0, len( links ), n ):
|
||||
cmd = ';'.join( 'ip link del %s' % link
|
||||
for link in links[ i : i + n ] )
|
||||
sh( '( %s ) 2> /dev/null' % cmd )
|
||||
|
||||
if 'tap9' in sh( 'ip link show' ):
|
||||
info( "*** Removing tap9 - assuming it's from cluster edition\n" )
|
||||
sh( 'ip link del tap9' )
|
||||
|
||||
info( "*** Killing stale mininet node processes\n" )
|
||||
killprocs( 'mininet:' )
|
||||
|
||||
info( "*** Shutting down stale tunnels\n" )
|
||||
killprocs( 'Tunnel=Ethernet' )
|
||||
killprocs( '.ssh/mn')
|
||||
sh( 'rm -f ~/.ssh/mn/*' )
|
||||
|
||||
# Call any additional cleanup code if necessary
|
||||
for callback in cls.callbacks:
|
||||
callback()
|
||||
|
||||
info( "*** Cleanup complete.\n" )
|
||||
|
||||
@classmethod
|
||||
def addCleanupCallback( cls, callback ):
|
||||
"Add cleanup callback"
|
||||
if callback not in cls.callbacks:
|
||||
cls.callbacks.append( callback )
|
||||
|
||||
|
||||
cleanup = Cleanup.cleanup
|
||||
addCleanupCallback = Cleanup.addCleanupCallback
|
||||
|
||||
+163
-63
@@ -31,10 +31,13 @@ from os import isatty
|
||||
from select import poll, POLLIN
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
import atexit
|
||||
|
||||
from mininet.log import info, output, error
|
||||
from mininet.term import makeTerms
|
||||
from mininet.util import quietRun, isShellBuiltin, dumpNodeConnections
|
||||
from mininet.term import makeTerms, runX11
|
||||
from mininet.util import ( quietRun, dumpNodeConnections,
|
||||
dumpPorts )
|
||||
|
||||
class CLI( Cmd ):
|
||||
"Simple command-line interface to talk to nodes."
|
||||
@@ -42,14 +45,13 @@ class CLI( Cmd ):
|
||||
prompt = 'mininet> '
|
||||
|
||||
def __init__( self, mininet, stdin=sys.stdin, script=None ):
|
||||
"""Start and run interactive or batch mode CLI
|
||||
mininet: Mininet network object
|
||||
stdin: standard input for CLI
|
||||
script: script to run in batch mode"""
|
||||
self.mn = mininet
|
||||
self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
|
||||
self.nodemap = {} # map names to Node objects
|
||||
for node in self.nodelist:
|
||||
self.nodemap[ node.name ] = node
|
||||
# Local variable bindings for py command
|
||||
self.locals = { 'net': mininet }
|
||||
self.locals.update( self.nodemap )
|
||||
# Attempt to handle input
|
||||
self.stdin = stdin
|
||||
self.inPoller = poll()
|
||||
@@ -57,31 +59,66 @@ class CLI( Cmd ):
|
||||
self.inputFile = script
|
||||
Cmd.__init__( self )
|
||||
info( '*** Starting CLI:\n' )
|
||||
|
||||
if self.inputFile:
|
||||
self.do_source( self.inputFile )
|
||||
return
|
||||
|
||||
self.initReadline()
|
||||
self.run()
|
||||
|
||||
readlineInited = False
|
||||
|
||||
@classmethod
|
||||
def initReadline( cls ):
|
||||
"Set up history if readline is available"
|
||||
# Only set up readline once to prevent multiplying the history file
|
||||
if cls.readlineInited:
|
||||
return
|
||||
cls.readlineInited = True
|
||||
try:
|
||||
from readline import ( read_history_file, write_history_file,
|
||||
set_history_length )
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
history_path = os.path.expanduser( '~/.mininet_history' )
|
||||
if os.path.isfile( history_path ):
|
||||
read_history_file( history_path )
|
||||
set_history_length( 1000 )
|
||||
atexit.register( lambda: write_history_file( history_path ) )
|
||||
|
||||
def run( self ):
|
||||
"Run our cmdloop(), catching KeyboardInterrupt"
|
||||
while True:
|
||||
try:
|
||||
# Make sure no nodes are still waiting
|
||||
for node in self.nodelist:
|
||||
for node in self.mn.values():
|
||||
while node.waiting:
|
||||
info( 'stopping', node, '\n' )
|
||||
node.sendInt()
|
||||
node.monitor()
|
||||
node.waitOutput()
|
||||
if self.isatty():
|
||||
quietRun( 'stty sane' )
|
||||
quietRun( 'stty echo sane intr ^C' )
|
||||
self.cmdloop()
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
output( '\nInterrupt\n' )
|
||||
# Output a message - unless it's also interrupted
|
||||
# pylint: disable=broad-except
|
||||
try:
|
||||
output( '\nInterrupt\n' )
|
||||
except Exception:
|
||||
pass
|
||||
# pylint: enable=broad-except
|
||||
|
||||
def emptyline( self ):
|
||||
"Don't repeat last command when you hit return."
|
||||
pass
|
||||
|
||||
# Disable pylint "Unused argument: 'arg's'" messages, as well as
|
||||
# "method could be a function" warning, since each CLI function
|
||||
# must have the same interface
|
||||
# pylint: disable-msg=R0201
|
||||
def getLocals( self ):
|
||||
"Local variable bindings for py command"
|
||||
self.locals.update( self.mn )
|
||||
return self.locals
|
||||
|
||||
helpStr = (
|
||||
'You may also send a command to a node using:\n'
|
||||
@@ -110,57 +147,63 @@ class CLI( Cmd ):
|
||||
|
||||
def do_nodes( self, _line ):
|
||||
"List all nodes."
|
||||
nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
|
||||
nodes = ' '.join( sorted( self.mn ) )
|
||||
output( 'available nodes are: \n%s\n' % nodes )
|
||||
|
||||
def do_ports( self, _line ):
|
||||
"display ports and interfaces for each switch"
|
||||
dumpPorts( self.mn.switches )
|
||||
|
||||
def do_net( self, _line ):
|
||||
"List network connections."
|
||||
dumpNodeConnections( self.nodelist )
|
||||
dumpNodeConnections( self.mn.values() )
|
||||
|
||||
def do_sh( self, line ):
|
||||
"Run an external shell command"
|
||||
"""Run an external shell command
|
||||
Usage: sh [cmd args]"""
|
||||
assert self # satisfy pylint and allow override
|
||||
call( line, shell=True )
|
||||
|
||||
# do_py() and do_px() need to catch any exception during eval()/exec()
|
||||
# pylint: disable-msg=W0703
|
||||
# pylint: disable=broad-except
|
||||
|
||||
def do_py( self, line ):
|
||||
"""Evaluate a Python expression.
|
||||
Node names may be used, e.g.: py h1.cmd('ls')"""
|
||||
try:
|
||||
result = eval( line, globals(), self.locals )
|
||||
result = eval( line, globals(), self.getLocals() )
|
||||
if not result:
|
||||
return
|
||||
elif isinstance( result, str ):
|
||||
output( result + '\n' )
|
||||
else:
|
||||
output( repr( result ) + '\n' )
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
output( str( e ) + '\n' )
|
||||
|
||||
# We are in fact using the exec() pseudo-function
|
||||
# pylint: disable-msg=W0122
|
||||
# pylint: disable=exec-used
|
||||
|
||||
def do_px( self, line ):
|
||||
"""Execute a Python statement.
|
||||
Node names may be used, e.g.: px print h1.cmd('ls')"""
|
||||
try:
|
||||
exec( line, globals(), self.locals )
|
||||
except Exception, e:
|
||||
exec( line, globals(), self.getLocals() )
|
||||
except Exception as e:
|
||||
output( str( e ) + '\n' )
|
||||
|
||||
# pylint: enable-msg=W0703,W0122
|
||||
# pylint: enable=broad-except,exec-used
|
||||
|
||||
def do_pingall( self, _line ):
|
||||
def do_pingall( self, line ):
|
||||
"Ping between all hosts."
|
||||
self.mn.pingAll()
|
||||
self.mn.pingAll( line )
|
||||
|
||||
def do_pingpair( self, _line ):
|
||||
"Ping between first two hosts, useful for testing."
|
||||
self.mn.pingPair()
|
||||
|
||||
def do_pingallfull( self, _line ):
|
||||
"Ping between first two hosts, returns all ping results."
|
||||
"Ping between all hosts, returns all ping results."
|
||||
self.mn.pingAllFull()
|
||||
|
||||
def do_pingpairfull( self, _line ):
|
||||
@@ -168,7 +211,8 @@ class CLI( Cmd ):
|
||||
self.mn.pingPairFull()
|
||||
|
||||
def do_iperf( self, line ):
|
||||
"Simple iperf TCP test between two (optionally specified) hosts."
|
||||
"""Simple iperf TCP test between two (optionally specified) hosts.
|
||||
Usage: iperf node1 node2"""
|
||||
args = line.split()
|
||||
if not args:
|
||||
self.mn.iperf()
|
||||
@@ -176,18 +220,19 @@ class CLI( Cmd ):
|
||||
hosts = []
|
||||
err = False
|
||||
for arg in args:
|
||||
if arg not in self.nodemap:
|
||||
if arg not in self.mn:
|
||||
err = True
|
||||
error( "node '%s' not in network\n" % arg )
|
||||
else:
|
||||
hosts.append( self.nodemap[ arg ] )
|
||||
hosts.append( self.mn[ arg ] )
|
||||
if not err:
|
||||
self.mn.iperf( hosts )
|
||||
else:
|
||||
error( 'invalid number of args: iperf src dst\n' )
|
||||
|
||||
def do_iperfudp( self, line ):
|
||||
"Simple iperf TCP test between two (optionally specified) hosts."
|
||||
"""Simple iperf UDP test between two (optionally specified) hosts.
|
||||
Usage: iperfudp bw node1 node2"""
|
||||
args = line.split()
|
||||
if not args:
|
||||
self.mn.iperf( l4Type='UDP' )
|
||||
@@ -196,11 +241,11 @@ class CLI( Cmd ):
|
||||
hosts = []
|
||||
err = False
|
||||
for arg in args[ 1:3 ]:
|
||||
if arg not in self.nodemap:
|
||||
if arg not in self.mn:
|
||||
err = True
|
||||
error( "node '%s' not in network\n" % arg )
|
||||
else:
|
||||
hosts.append( self.nodemap[ arg ] )
|
||||
hosts.append( self.mn[ arg ] )
|
||||
if not err:
|
||||
self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
|
||||
else:
|
||||
@@ -209,17 +254,18 @@ class CLI( Cmd ):
|
||||
|
||||
def do_intfs( self, _line ):
|
||||
"List interfaces."
|
||||
for node in self.nodelist:
|
||||
for node in self.mn.values():
|
||||
output( '%s: %s\n' %
|
||||
( node.name, ','.join( node.intfNames() ) ) )
|
||||
|
||||
def do_dump( self, _line ):
|
||||
"Dump node info."
|
||||
for node in self.nodelist:
|
||||
for node in self.mn.values():
|
||||
output( '%s\n' % repr( node ) )
|
||||
|
||||
def do_link( self, line ):
|
||||
"Bring link(s) between two nodes up or down."
|
||||
"""Bring link(s) between two nodes up or down.
|
||||
Usage: link node1 node2 [up/down]"""
|
||||
args = line.split()
|
||||
if len(args) != 3:
|
||||
error( 'invalid number of args: link end1 end2 [up down]\n' )
|
||||
@@ -229,24 +275,39 @@ class CLI( Cmd ):
|
||||
self.mn.configLinkStatus( *args )
|
||||
|
||||
def do_xterm( self, line, term='xterm' ):
|
||||
"Spawn xterm(s) for the given node(s)."
|
||||
"""Spawn xterm(s) for the given node(s).
|
||||
Usage: xterm node1 node2 ..."""
|
||||
args = line.split()
|
||||
if not args:
|
||||
error( 'usage: %s node1 node2 ...\n' % term )
|
||||
else:
|
||||
for arg in args:
|
||||
if arg not in self.nodemap:
|
||||
if arg not in self.mn:
|
||||
error( "node '%s' not in network\n" % arg )
|
||||
else:
|
||||
node = self.nodemap[ arg ]
|
||||
node = self.mn[ arg ]
|
||||
self.mn.terms += makeTerms( [ node ], term = term )
|
||||
|
||||
def do_x( self, line ):
|
||||
"""Create an X11 tunnel to the given node,
|
||||
optionally starting a client.
|
||||
Usage: x node [cmd args]"""
|
||||
args = line.split()
|
||||
if not args:
|
||||
error( 'usage: x node [cmd args]...\n' )
|
||||
else:
|
||||
node = self.mn[ args[ 0 ] ]
|
||||
cmd = args[ 1: ]
|
||||
self.mn.terms += runX11( node, cmd )
|
||||
|
||||
def do_gterm( self, line ):
|
||||
"Spawn gnome-terminal(s) for the given node(s)."
|
||||
"""Spawn gnome-terminal(s) for the given node(s).
|
||||
Usage: gterm node1 node2 ..."""
|
||||
self.do_xterm( line, term='gterm' )
|
||||
|
||||
def do_exit( self, _line ):
|
||||
"Exit"
|
||||
assert self # satisfy pylint and allow override
|
||||
return 'exited by user command'
|
||||
|
||||
def do_quit( self, line ):
|
||||
@@ -263,7 +324,8 @@ class CLI( Cmd ):
|
||||
return isatty( self.stdin.fileno() )
|
||||
|
||||
def do_noecho( self, line ):
|
||||
"Run an interactive command with echoing turned off."
|
||||
"""Run an interactive command with echoing turned off.
|
||||
Usage: noecho [cmd args]"""
|
||||
if self.isatty():
|
||||
quietRun( 'stty -echo' )
|
||||
self.default( line )
|
||||
@@ -271,7 +333,8 @@ class CLI( Cmd ):
|
||||
quietRun( 'stty echo' )
|
||||
|
||||
def do_source( self, line ):
|
||||
"Read commands from an input file."
|
||||
"""Read commands from an input file.
|
||||
Usage: source <file>"""
|
||||
args = line.split()
|
||||
if len(args) != 1:
|
||||
error( 'usage: source <file>\n' )
|
||||
@@ -286,10 +349,12 @@ class CLI( Cmd ):
|
||||
break
|
||||
except IOError:
|
||||
error( 'error reading file %s\n' % args[ 0 ] )
|
||||
self.inputFile.close()
|
||||
self.inputFile = None
|
||||
|
||||
def do_dpctl( self, line ):
|
||||
"Run dpctl command on all switches."
|
||||
"""Run dpctl (or ovs-ofctl) command on all switches.
|
||||
Usage: dpctl command [arg1] [arg2] ..."""
|
||||
args = line.split()
|
||||
if len(args) < 1:
|
||||
error( 'usage: dpctl command [arg1] [arg2] ...\n' )
|
||||
@@ -305,37 +370,62 @@ class CLI( Cmd ):
|
||||
elapsed = time.time() - start
|
||||
self.stdout.write("*** Elapsed time: %0.6f secs\n" % elapsed)
|
||||
|
||||
def do_links( self, _line ):
|
||||
"Report on links"
|
||||
for link in self.mn.links:
|
||||
output( link, link.status(), '\n' )
|
||||
|
||||
def do_switch( self, line ):
|
||||
"Starts or stops a switch"
|
||||
args = line.split()
|
||||
if len(args) != 2:
|
||||
error( 'invalid number of args: switch <switch name>'
|
||||
'{start, stop}\n' )
|
||||
return
|
||||
sw = args[ 0 ]
|
||||
command = args[ 1 ]
|
||||
if sw not in self.mn or self.mn.get( sw ) not in self.mn.switches:
|
||||
error( 'invalid switch: %s\n' % args[ 1 ] )
|
||||
else:
|
||||
sw = args[ 0 ]
|
||||
command = args[ 1 ]
|
||||
if command == 'start':
|
||||
self.mn.get( sw ).start( self.mn.controllers )
|
||||
elif command == 'stop':
|
||||
self.mn.get( sw ).stop( deleteIntfs=False )
|
||||
else:
|
||||
error( 'invalid command: '
|
||||
'switch <switch name> {start, stop}\n' )
|
||||
|
||||
def default( self, line ):
|
||||
"""Called on an input line when the command prefix is not recognized.
|
||||
Overridden to run shell commands when a node is the first CLI argument.
|
||||
Past the first CLI argument, node names are automatically replaced with
|
||||
corresponding IP addrs."""
|
||||
Overridden to run shell commands when a node is the first
|
||||
CLI argument. Past the first CLI argument, node names are
|
||||
automatically replaced with corresponding IP addrs."""
|
||||
|
||||
first, args, line = self.parseline( line )
|
||||
if not args:
|
||||
return
|
||||
if args and len(args) > 0 and args[ -1 ] == '\n':
|
||||
args = args[ :-1 ]
|
||||
rest = args.split( ' ' )
|
||||
|
||||
if first in self.nodemap:
|
||||
node = self.nodemap[ first ]
|
||||
if first in self.mn:
|
||||
if not args:
|
||||
error( '*** Please enter a command for node: %s <cmd>\n'
|
||||
% first )
|
||||
return
|
||||
node = self.mn[ first ]
|
||||
rest = args.split( ' ' )
|
||||
# Substitute IP addresses for node names in command
|
||||
rest = [ self.nodemap[ arg ].IP()
|
||||
if arg in self.nodemap else arg
|
||||
# If updateIP() returns None, then use node name
|
||||
rest = [ self.mn[ arg ].defaultIntf().updateIP() or arg
|
||||
if arg in self.mn else arg
|
||||
for arg in rest ]
|
||||
rest = ' '.join( rest )
|
||||
# Run cmd on node:
|
||||
builtin = isShellBuiltin( first )
|
||||
node.sendCmd( rest, printPid=( not builtin ) )
|
||||
node.sendCmd( rest )
|
||||
self.waitForNode( node )
|
||||
else:
|
||||
error( '*** Unknown command: %s\n' % first )
|
||||
|
||||
# pylint: enable-msg=R0201
|
||||
error( '*** Unknown command: %s\n' % line )
|
||||
|
||||
def waitForNode( self, node ):
|
||||
"Wait for a node to finish, and print its output."
|
||||
"Wait for a node to finish, and print its output."
|
||||
# Pollers
|
||||
nodePoller = poll()
|
||||
nodePoller.register( node.stdout )
|
||||
@@ -353,7 +443,7 @@ class CLI( Cmd ):
|
||||
if False and self.inputFile:
|
||||
key = self.inputFile.read( 1 )
|
||||
if key is not '':
|
||||
node.write(key)
|
||||
node.write( key )
|
||||
else:
|
||||
self.inputFile = None
|
||||
if isReadable( self.inPoller ):
|
||||
@@ -365,8 +455,18 @@ class CLI( Cmd ):
|
||||
if not node.waiting:
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
# There is an at least one race condition here, since
|
||||
# it's possible to interrupt ourselves after we've
|
||||
# read data but before it has been printed.
|
||||
node.sendInt()
|
||||
|
||||
def precmd( self, line ):
|
||||
"allow for comments in the cli"
|
||||
if '#' in line:
|
||||
line = line.split( '#' )[ 0 ]
|
||||
return line
|
||||
|
||||
|
||||
# Helper functions
|
||||
|
||||
def isReadable( poller ):
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../examples
|
||||
+236
-75
@@ -25,15 +25,15 @@ Link: basic link class for creating veth pairs
|
||||
"""
|
||||
|
||||
from mininet.log import info, error, debug
|
||||
from mininet.util import makeIntfPair, errFail, quietRun
|
||||
from time import sleep
|
||||
from mininet.util import makeIntfPair
|
||||
import re
|
||||
|
||||
class Intf( object ):
|
||||
|
||||
"Basic interface object that can configure itself."
|
||||
|
||||
def __init__( self, name, node=None, port=None, link=None, **params ):
|
||||
def __init__( self, name, node=None, port=None, link=None,
|
||||
mac=None, **params ):
|
||||
"""name: interface name (e.g. h1-eth0)
|
||||
node: owning node (where this intf most likely lives)
|
||||
link: parent link if we're part of a link
|
||||
@@ -41,9 +41,21 @@ class Intf( object ):
|
||||
self.node = node
|
||||
self.name = name
|
||||
self.link = link
|
||||
self.mac, self.ip, self.prefixLen = None, None, None
|
||||
self.mac = mac
|
||||
self.ip, self.prefixLen = None, None
|
||||
|
||||
# if interface is lo, we know the ip is 127.0.0.1.
|
||||
# This saves an ifconfig command per node
|
||||
if self.name == 'lo':
|
||||
self.ip = '127.0.0.1'
|
||||
self.prefixLen = 8
|
||||
# Add to node (and move ourselves if necessary )
|
||||
node.addIntf( self, port=port )
|
||||
if node:
|
||||
moveIntfFn = params.pop( 'moveIntfFn', None )
|
||||
if moveIntfFn:
|
||||
node.addIntf( self, port=port, moveIntfFn=moveIntfFn )
|
||||
else:
|
||||
node.addIntf( self, port=port )
|
||||
# Save params for future reference
|
||||
self.params = params
|
||||
self.config( **params )
|
||||
@@ -64,6 +76,9 @@ class Intf( object ):
|
||||
self.ip, self.prefixLen = ipstr.split( '/' )
|
||||
return self.ifconfig( ipstr, 'up' )
|
||||
else:
|
||||
if prefixLen is None:
|
||||
raise Exception( 'No prefix length set for IP address %s'
|
||||
% ( ipstr, ) )
|
||||
self.ip, self.prefixLen = ipstr, prefixLen
|
||||
return self.ifconfig( '%s/%s' % ( ipstr, prefixLen ) )
|
||||
|
||||
@@ -80,7 +95,10 @@ class Intf( object ):
|
||||
|
||||
def updateIP( self ):
|
||||
"Return updated IP address based on ifconfig"
|
||||
ifconfig = self.ifconfig()
|
||||
# use pexec instead of node.cmd so that we dont read
|
||||
# backgrounded output from the cli.
|
||||
ifconfig, _err, _exitCode = self.node.pexec(
|
||||
'ifconfig %s' % self.name )
|
||||
ips = self._ipMatchRegex.findall( ifconfig )
|
||||
self.ip = ips[ 0 ] if ips else None
|
||||
return self.ip
|
||||
@@ -92,6 +110,19 @@ class Intf( object ):
|
||||
self.mac = macs[ 0 ] if macs else None
|
||||
return self.mac
|
||||
|
||||
# Instead of updating ip and mac separately,
|
||||
# use one ifconfig call to do it simultaneously.
|
||||
# This saves an ifconfig command, which improves performance.
|
||||
|
||||
def updateAddr( self ):
|
||||
"Return IP address and MAC address based on ifconfig."
|
||||
ifconfig = self.ifconfig()
|
||||
ips = self._ipMatchRegex.findall( ifconfig )
|
||||
macs = self._macMatchRegex.findall( ifconfig )
|
||||
self.ip = ips[ 0 ] if ips else None
|
||||
self.mac = macs[ 0 ] if macs else None
|
||||
return self.ip, self.mac
|
||||
|
||||
def IP( self ):
|
||||
"Return IP address"
|
||||
return self.ip
|
||||
@@ -103,13 +134,20 @@ class Intf( object ):
|
||||
def isUp( self, setUp=False ):
|
||||
"Return whether interface is up"
|
||||
if setUp:
|
||||
self.ifconfig( 'up' )
|
||||
return "UP" in self.ifconfig()
|
||||
cmdOutput = self.ifconfig( 'up' )
|
||||
# no output indicates success
|
||||
if cmdOutput:
|
||||
error( "Error setting %s up: %s " % ( self.name, cmdOutput ) )
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
return "UP" in self.ifconfig()
|
||||
|
||||
def rename( self, newname ):
|
||||
"Rename interface"
|
||||
self.ifconfig( 'down' )
|
||||
result = self.cmd( 'ip link set dev', self.name, 'name', newname )
|
||||
result = self.cmd( 'ip link set', self.name, 'name', newname )
|
||||
self.name = newname
|
||||
self.ifconfig( 'up' )
|
||||
return result
|
||||
@@ -126,13 +164,13 @@ class Intf( object ):
|
||||
method: config method name
|
||||
param: arg=value (ignore if value=None)
|
||||
value may also be list or dict"""
|
||||
name, value = param.items()[ 0 ]
|
||||
name, value = list( param.items() )[ 0 ]
|
||||
f = getattr( self, method, None )
|
||||
if not f or value is None:
|
||||
return
|
||||
if type( value ) is list:
|
||||
if isinstance( value, list ):
|
||||
result = f( *value )
|
||||
elif type( value ) is dict:
|
||||
elif isinstance( value, dict ):
|
||||
result = f( **value )
|
||||
else:
|
||||
result = f( value )
|
||||
@@ -155,15 +193,25 @@ class Intf( object ):
|
||||
self.setParam( r, 'setIP', ip=ip )
|
||||
self.setParam( r, 'isUp', up=up )
|
||||
self.setParam( r, 'ifconfig', ifconfig=ifconfig )
|
||||
self.updateIP()
|
||||
self.updateMAC()
|
||||
return r
|
||||
|
||||
def delete( self ):
|
||||
"Delete interface"
|
||||
self.cmd( 'ip link del ' + self.name )
|
||||
# Does it help to sleep to let things run?
|
||||
sleep( 0.001 )
|
||||
# We used to do this, but it slows us down:
|
||||
# if self.node.inNamespace:
|
||||
# Link may have been dumped into root NS
|
||||
# quietRun( 'ip link del ' + self.name )
|
||||
self.node.delIntf( self )
|
||||
self.link = None
|
||||
|
||||
def status( self ):
|
||||
"Return intf status as a string"
|
||||
links, _err, _result = self.node.pexec( 'ip link show' )
|
||||
if self.name in links:
|
||||
return "OK"
|
||||
else:
|
||||
return "MISSING"
|
||||
|
||||
def __repr__( self ):
|
||||
return '<%s %s>' % ( self.__class__.__name__, self.name )
|
||||
@@ -177,15 +225,19 @@ class TCIntf( Intf ):
|
||||
Allows specification of bandwidth limits (various methods)
|
||||
as well as delay, loss and max queue length"""
|
||||
|
||||
# The parameters we use seem to work reasonably up to 1 Gb/sec
|
||||
# For higher data rates, we will probably need to change them.
|
||||
bwParamMax = 1000
|
||||
|
||||
def bwCmds( self, bw=None, speedup=0, use_hfsc=False, use_tbf=False,
|
||||
latency_ms=None, enable_ecn=False, enable_red=False ):
|
||||
"Return tc commands to set bandwidth"
|
||||
|
||||
cmds, parent = [], ' root '
|
||||
|
||||
if bw and ( bw < 0 or bw > 1000 ):
|
||||
error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' )
|
||||
|
||||
if bw and ( bw < 0 or bw > self.bwParamMax ):
|
||||
error( 'Bandwidth limit', bw, 'is outside supported range 0..%d'
|
||||
% self.bwParamMax, '- ignoring\n' )
|
||||
elif bw is not None:
|
||||
# BL: this seems a bit brittle...
|
||||
if ( speedup > 0 and
|
||||
@@ -201,7 +253,7 @@ class TCIntf( Intf ):
|
||||
+ 'rate %fMbit ul rate %fMbit' % ( bw, bw ) ]
|
||||
elif use_tbf:
|
||||
if latency_ms is None:
|
||||
latency_ms = 15 * 8 / bw
|
||||
latency_ms = 15.0 * 8 / bw
|
||||
cmds += [ '%s qdisc add dev %s root handle 5: tbf ' +
|
||||
'rate %fMbit burst 15000 latency %fms' %
|
||||
( bw, latency_ms ) ]
|
||||
@@ -233,18 +285,14 @@ class TCIntf( Intf ):
|
||||
loss=None, max_queue_size=None ):
|
||||
"Internal method: return tc commands for delay and loss"
|
||||
cmds = []
|
||||
if delay and delay < 0:
|
||||
error( 'Negative delay', delay, '\n' )
|
||||
elif jitter and jitter < 0:
|
||||
error( 'Negative jitter', jitter, '\n' )
|
||||
elif loss and ( loss < 0 or loss > 100 ):
|
||||
if loss and ( loss < 0 or loss > 100 ):
|
||||
error( 'Bad loss percentage', loss, '%%\n' )
|
||||
else:
|
||||
# Delay/jitter/loss/max queue size
|
||||
netemargs = '%s%s%s%s' % (
|
||||
'delay %s ' % delay if delay is not None else '',
|
||||
'%s ' % jitter if jitter is not None else '',
|
||||
'loss %d ' % loss if loss is not None else '',
|
||||
'loss %.5f ' % loss if loss is not None else '',
|
||||
'limit %d' % max_queue_size if max_queue_size is not None
|
||||
else '' )
|
||||
if netemargs:
|
||||
@@ -261,16 +309,40 @@ class TCIntf( Intf ):
|
||||
return self.cmd( c )
|
||||
|
||||
def config( self, bw=None, delay=None, jitter=None, loss=None,
|
||||
disable_gro=True, speedup=0, use_hfsc=False, use_tbf=False,
|
||||
gro=False, txo=True, rxo=True,
|
||||
speedup=0, use_hfsc=False, use_tbf=False,
|
||||
latency_ms=None, enable_ecn=False, enable_red=False,
|
||||
max_queue_size=None, **params ):
|
||||
"Configure the port and set its properties."
|
||||
"""Configure the port and set its properties.
|
||||
bw: bandwidth in b/s (e.g. '10m')
|
||||
delay: transmit delay (e.g. '1ms' )
|
||||
jitter: jitter (e.g. '1ms')
|
||||
loss: loss (e.g. '1%' )
|
||||
gro: enable GRO (False)
|
||||
txo: enable transmit checksum offload (True)
|
||||
rxo: enable receive checksum offload (True)
|
||||
speedup: experimental switch-side bw option
|
||||
use_hfsc: use HFSC scheduling
|
||||
use_tbf: use TBF scheduling
|
||||
latency_ms: TBF latency parameter
|
||||
enable_ecn: enable ECN (False)
|
||||
enable_red: enable RED (False)
|
||||
max_queue_size: queue limit parameter for netem"""
|
||||
|
||||
# Support old names for parameters
|
||||
gro = not params.pop( 'disable_gro', not gro )
|
||||
|
||||
result = Intf.config( self, **params)
|
||||
|
||||
# Disable GRO
|
||||
if disable_gro:
|
||||
self.cmd( 'ethtool -K %s gro off' % self )
|
||||
def on( isOn ):
|
||||
"Helper method: bool -> 'on'/'off'"
|
||||
return 'on' if isOn else 'off'
|
||||
|
||||
# Set offload parameters with ethool
|
||||
self.cmd( 'ethtool -K', self,
|
||||
'gro', on( gro ),
|
||||
'tx', on( txo ),
|
||||
'rx', on( rxo ) )
|
||||
|
||||
# Optimization: return if nothing else to configure
|
||||
# Question: what happens if we want to reset things?
|
||||
@@ -279,7 +351,11 @@ class TCIntf( Intf ):
|
||||
return
|
||||
|
||||
# Clear existing configuration
|
||||
cmds = [ '%s qdisc del dev %s root' ]
|
||||
tcoutput = self.tc( '%s qdisc show dev %s' )
|
||||
if "priomap" not in tcoutput and "noqueue" not in tcoutput:
|
||||
cmds = [ '%s qdisc del dev %s root' ]
|
||||
else:
|
||||
cmds = []
|
||||
|
||||
# Bandwidth limits via various methods
|
||||
bwcmds, parent = self.bwCmds( bw=bw, speedup=speedup,
|
||||
@@ -290,16 +366,17 @@ class TCIntf( Intf ):
|
||||
cmds += bwcmds
|
||||
|
||||
# Delay/jitter/loss/max_queue_size using netem
|
||||
delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter, loss=loss,
|
||||
max_queue_size=max_queue_size,
|
||||
parent=parent )
|
||||
delaycmds, parent = self.delayCmds( delay=delay, jitter=jitter,
|
||||
loss=loss,
|
||||
max_queue_size=max_queue_size,
|
||||
parent=parent )
|
||||
cmds += delaycmds
|
||||
|
||||
# Ugly but functional: display configuration info
|
||||
stuff = ( ( [ '%.2fMbit' % bw ] if bw is not None else [] ) +
|
||||
( [ '%s delay' % delay ] if delay is not None else [] ) +
|
||||
( [ '%s jitter' % jitter ] if jitter is not None else [] ) +
|
||||
( ['%d%% loss' % loss ] if loss is not None else [] ) +
|
||||
( ['%.5f%% loss' % loss ] if loss is not None else [] ) +
|
||||
( [ 'ECN' ] if enable_ecn else [ 'RED' ]
|
||||
if enable_red else [] ) )
|
||||
info( '(' + ' '.join( stuff ) + ') ' )
|
||||
@@ -307,6 +384,9 @@ class TCIntf( Intf ):
|
||||
# Execute all the commands in our node
|
||||
debug("at map stage w/cmds: %s\n" % cmds)
|
||||
tcoutputs = [ self.tc(cmd) for cmd in cmds ]
|
||||
for output in tcoutputs:
|
||||
if output != '':
|
||||
error( "*** Error: %s" % output )
|
||||
debug( "cmds:", cmds, '\n' )
|
||||
debug( "outputs:", tcoutputs, '\n' )
|
||||
result[ 'tcoutputs'] = tcoutputs
|
||||
@@ -320,10 +400,11 @@ class Link( object ):
|
||||
"""A basic link is just a veth pair.
|
||||
Other types of links could be tunnels, link emulators, etc.."""
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def __init__( self, node1, node2, port1=None, port2=None,
|
||||
intfName1=None, intfName2=None,
|
||||
intfName1=None, intfName2=None, addr1=None, addr2=None,
|
||||
intf=Intf, cls1=None, cls2=None, params1=None,
|
||||
params2=None ):
|
||||
params2=None, fast=True ):
|
||||
"""Create veth link to another node, making two new interfaces.
|
||||
node1: first node
|
||||
node2: second node
|
||||
@@ -336,77 +417,157 @@ class Link( object ):
|
||||
params1: parameters for interface 1
|
||||
params2: parameters for interface 2"""
|
||||
# This is a bit awkward; it seems that having everything in
|
||||
# params would be more orthogonal, but being able to specify
|
||||
# in-line arguments is more convenient!
|
||||
if port1 is None:
|
||||
port1 = node1.newPort()
|
||||
if port2 is None:
|
||||
port2 = node2.newPort()
|
||||
# params is more orthogonal, but being able to specify
|
||||
# in-line arguments is more convenient! So we support both.
|
||||
if params1 is None:
|
||||
params1 = {}
|
||||
if params2 is None:
|
||||
params2 = {}
|
||||
# Allow passing in params1=params2
|
||||
if params2 is params1:
|
||||
params2 = dict( params1 )
|
||||
if port1 is not None:
|
||||
params1[ 'port' ] = port1
|
||||
if port2 is not None:
|
||||
params2[ 'port' ] = port2
|
||||
if 'port' not in params1:
|
||||
params1[ 'port' ] = node1.newPort()
|
||||
if 'port' not in params2:
|
||||
params2[ 'port' ] = node2.newPort()
|
||||
if not intfName1:
|
||||
intfName1 = self.intfName( node1, port1 )
|
||||
intfName1 = self.intfName( node1, params1[ 'port' ] )
|
||||
if not intfName2:
|
||||
intfName2 = self.intfName( node2, port2 )
|
||||
intfName2 = self.intfName( node2, params2[ 'port' ] )
|
||||
|
||||
self.makeIntfPair( intfName1, intfName2, node1, node2 )
|
||||
self.fast = fast
|
||||
if fast:
|
||||
params1.setdefault( 'moveIntfFn', self._ignore )
|
||||
params2.setdefault( 'moveIntfFn', self._ignore )
|
||||
self.makeIntfPair( intfName1, intfName2, addr1, addr2,
|
||||
node1, node2, deleteIntfs=False )
|
||||
else:
|
||||
self.makeIntfPair( intfName1, intfName2, addr1, addr2 )
|
||||
|
||||
if not cls1:
|
||||
cls1 = intf
|
||||
if not cls2:
|
||||
cls2 = intf
|
||||
if not params1:
|
||||
params1 = {}
|
||||
if not params2:
|
||||
params2 = {}
|
||||
|
||||
intf1 = cls1( name=intfName1, node=node1, port=port1,
|
||||
link=self, **params1 )
|
||||
intf2 = cls2( name=intfName2, node=node2, port=port2,
|
||||
link=self, **params2 )
|
||||
intf1 = cls1( name=intfName1, node=node1,
|
||||
link=self, mac=addr1, **params1 )
|
||||
intf2 = cls2( name=intfName2, node=node2,
|
||||
link=self, mac=addr2, **params2 )
|
||||
|
||||
# All we are is dust in the wind, and our two interfaces
|
||||
self.intf1, self.intf2 = intf1, intf2
|
||||
# pylint: enable=too-many-branches
|
||||
|
||||
@classmethod
|
||||
def intfName( cls, node, n ):
|
||||
@staticmethod
|
||||
def _ignore( *args, **kwargs ):
|
||||
"Ignore any arguments"
|
||||
pass
|
||||
|
||||
def intfName( self, node, n ):
|
||||
"Construct a canonical interface name node-ethN for interface n."
|
||||
# Leave this as an instance method for now
|
||||
assert self
|
||||
return node.name + '-eth' + repr( n )
|
||||
|
||||
@classmethod
|
||||
def makeIntfPair( cls, intf1, intf2, node1=None, node2=None ):
|
||||
def makeIntfPair( cls, intfname1, intfname2, addr1=None, addr2=None,
|
||||
node1=None, node2=None, deleteIntfs=True ):
|
||||
"""Create pair of interfaces
|
||||
intf1: name of interface 1
|
||||
intf2: name of interface 2
|
||||
(override this class method [and possibly delete()]
|
||||
intfname1: name for interface 1
|
||||
intfname2: name for interface 2
|
||||
addr1: MAC address for interface 1 (optional)
|
||||
addr2: MAC address for interface 2 (optional)
|
||||
node1: home node for interface 1 (optional)
|
||||
node2: home node for interface 2 (optional)
|
||||
(override this method [and possibly delete()]
|
||||
to change link type)"""
|
||||
# To be compatible with pid namespaces and chroot in the future
|
||||
# we create links in the root namespace and then move
|
||||
# the ends as needed.
|
||||
# First, make sure there aren't stale links sitting around
|
||||
quietRun( 'ip link delete %s type veth' % intf1 )
|
||||
quietRun( 'ip link delete %s type veth' % intf2 )
|
||||
cmd = 'ip link add %s type veth peer name %s' % ( intf1, intf2 )
|
||||
if node2 and node2.inNamespace:
|
||||
cmd += ' netns %s' % node2
|
||||
errFail( cmd )
|
||||
if node1 and node1.inNamespace:
|
||||
errFail( 'ip link set dev %s netns %s' % ( intf1, node1 ) )
|
||||
# Leave this as a class method for now
|
||||
assert cls
|
||||
return makeIntfPair( intfname1, intfname2, addr1, addr2, node1, node2,
|
||||
deleteIntfs=deleteIntfs )
|
||||
|
||||
def delete( self ):
|
||||
"Delete this link"
|
||||
self.intf1.delete()
|
||||
self.intf1 = None
|
||||
self.intf2.delete()
|
||||
self.intf2 = None
|
||||
|
||||
def stop( self ):
|
||||
"Override to stop and clean up link as needed"
|
||||
self.delete()
|
||||
|
||||
def status( self ):
|
||||
"Return link status as a string"
|
||||
return "(%s %s)" % ( self.intf1.status(), self.intf2.status() )
|
||||
|
||||
def __str__( self ):
|
||||
return '%s<->%s' % ( self.intf1, self.intf2 )
|
||||
|
||||
|
||||
class OVSIntf( Intf ):
|
||||
"Patch interface on an OVSSwitch"
|
||||
|
||||
def ifconfig( self, *args ):
|
||||
cmd = ' '.join( args )
|
||||
if cmd == 'up':
|
||||
# OVSIntf is always up
|
||||
return
|
||||
else:
|
||||
raise Exception( 'OVSIntf cannot do ifconfig ' + cmd )
|
||||
|
||||
|
||||
class OVSLink( Link ):
|
||||
"""Link that makes patch links between OVSSwitches
|
||||
Warning: in testing we have found that no more
|
||||
than ~64 OVS patch links should be used in row."""
|
||||
|
||||
def __init__( self, node1, node2, **kwargs ):
|
||||
"See Link.__init__() for options"
|
||||
from mininet.node import OVSSwitch
|
||||
self.isPatchLink = False
|
||||
if ( isinstance( node1, OVSSwitch ) and
|
||||
isinstance( node2, OVSSwitch ) ):
|
||||
self.isPatchLink = True
|
||||
kwargs.update( cls1=OVSIntf, cls2=OVSIntf )
|
||||
Link.__init__( self, node1, node2, **kwargs )
|
||||
|
||||
def makeIntfPair( self, *args, **kwargs ):
|
||||
"Usually delegated to OVSSwitch"
|
||||
if self.isPatchLink:
|
||||
return None, None
|
||||
else:
|
||||
return Link.makeIntfPair( *args, **kwargs )
|
||||
|
||||
|
||||
class TCLink( Link ):
|
||||
"Link with symmetric TC interfaces configured via opts"
|
||||
def __init__( self, node1, node2, port1=None, port2=None,
|
||||
intfName1=None, intfName2=None, **params ):
|
||||
intfName1=None, intfName2=None,
|
||||
addr1=None, addr2=None, **params ):
|
||||
Link.__init__( self, node1, node2, port1=port1, port2=port2,
|
||||
intfName1=intfName1, intfName2=intfName2,
|
||||
cls1=TCIntf,
|
||||
cls2=TCIntf,
|
||||
addr1=addr1, addr2=addr2,
|
||||
params1=params,
|
||||
params2=params)
|
||||
params2=params )
|
||||
|
||||
|
||||
class TCULink( TCLink ):
|
||||
"""TCLink with default settings optimized for UserSwitch
|
||||
(txo=rxo=0/False). Unfortunately with recent Linux kernels,
|
||||
enabling TX and RX checksum offload on veth pairs doesn't work
|
||||
well with UserSwitch: either it gets terrible performance or
|
||||
TCP packets with bad checksums are generated, forwarded, and
|
||||
*dropped* due to having bad checksums! OVS and LinuxBridge seem
|
||||
to cope with this somehow, but it is likely to be an issue with
|
||||
many software Ethernet bridges."""
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
kwargs.update( txo=False, rxo=False )
|
||||
TCLink.__init__( self, *args, **kwargs )
|
||||
|
||||
+18
-19
@@ -57,21 +57,19 @@ class StreamHandlerNoNewline( logging.StreamHandler ):
|
||||
|
||||
class Singleton( type ):
|
||||
"""Singleton pattern from Wikipedia
|
||||
See http://en.wikipedia.org/wiki/SingletonPattern#Python
|
||||
See http://en.wikipedia.org/wiki/Singleton_Pattern
|
||||
|
||||
Intended to be used as a __metaclass_ param, as shown for the class
|
||||
below.
|
||||
below."""
|
||||
|
||||
Changed cls first args to mcs to satisfy pylint."""
|
||||
def __init__( cls, name, bases, dict_ ):
|
||||
super( Singleton, cls ).__init__( name, bases, dict_ )
|
||||
cls.instance = None
|
||||
|
||||
def __init__( mcs, name, bases, dict_ ):
|
||||
super( Singleton, mcs ).__init__( name, bases, dict_ )
|
||||
mcs.instance = None
|
||||
|
||||
def __call__( mcs, *args, **kw ):
|
||||
if mcs.instance is None:
|
||||
mcs.instance = super( Singleton, mcs ).__call__( *args, **kw )
|
||||
return mcs.instance
|
||||
def __call__( cls, *args, **kw ):
|
||||
if cls.instance is None:
|
||||
cls.instance = super( Singleton, cls ).__call__( *args, **kw )
|
||||
return cls.instance
|
||||
|
||||
|
||||
class MininetLogger( Logger, object ):
|
||||
@@ -126,8 +124,8 @@ class MininetLogger( Logger, object ):
|
||||
self.setLevel( level )
|
||||
self.handlers[ 0 ].setLevel( level )
|
||||
|
||||
# pylint: disable-msg=E0202
|
||||
# "An attribute inherited from mininet.log hide this method"
|
||||
# pylint: disable=method-hidden
|
||||
# "An attribute inherited from mininet.log hide this method" (sic)
|
||||
# Not sure why this is occurring - this function definitely gets called.
|
||||
|
||||
# See /usr/lib/python2.5/logging/__init__.py; modified from warning()
|
||||
@@ -144,7 +142,7 @@ class MininetLogger( Logger, object ):
|
||||
if self.isEnabledFor( OUTPUT ):
|
||||
self._log( OUTPUT, msg, args, kwargs )
|
||||
|
||||
# pylint: enable-msg=E0202
|
||||
# pylint: enable=method-hidden
|
||||
|
||||
lg = MininetLogger()
|
||||
|
||||
@@ -162,7 +160,7 @@ def makeListCompatible( fn ):
|
||||
"Generated function. Closure-ish."
|
||||
if len( args ) == 1:
|
||||
return fn( *args )
|
||||
args = ' '.join( [ str( arg ) for arg in args ] )
|
||||
args = ' '.join( str( arg ) for arg in args )
|
||||
return fn( args )
|
||||
|
||||
# Fix newfn's name and docstring
|
||||
@@ -170,9 +168,10 @@ def makeListCompatible( fn ):
|
||||
setattr( newfn, '__doc__', fn.__doc__ )
|
||||
return newfn
|
||||
|
||||
info, output, warn, error, debug = (
|
||||
lg.info, lg.output, lg.warn, lg.error, lg.debug ) = [
|
||||
makeListCompatible( f ) for f in
|
||||
lg.info, lg.output, lg.warn, lg.error, lg.debug ]
|
||||
_loggers = lg.info, lg.output, lg.warn, lg.error, lg.debug
|
||||
_loggers = tuple( makeListCompatible( logger )
|
||||
for logger in _loggers )
|
||||
lg.info, lg.output, lg.warn, lg.error, lg.debug = _loggers
|
||||
info, output, warn, error, debug = _loggers
|
||||
|
||||
setLogLevel = lg.setLogLevel
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"Module dependency utility functions for Mininet."
|
||||
|
||||
from mininet.util import quietRun
|
||||
from mininet.util import quietRun, BaseString
|
||||
from mininet.log import info, error, debug
|
||||
from os import environ
|
||||
|
||||
@@ -28,9 +28,9 @@ def moduleDeps( subtract=None, add=None ):
|
||||
add: string or list of module names to add, if not already loaded"""
|
||||
subtract = subtract if subtract is not None else []
|
||||
add = add if add is not None else []
|
||||
if type( subtract ) is str:
|
||||
if isinstance( subtract, BaseString ):
|
||||
subtract = [ subtract ]
|
||||
if type( add ) is str:
|
||||
if isinstance( add, BaseString ):
|
||||
add = [ add ]
|
||||
for mod in subtract:
|
||||
if mod in lsmod():
|
||||
|
||||
+345
-123
@@ -90,29 +90,35 @@ import os
|
||||
import re
|
||||
import select
|
||||
import signal
|
||||
import random
|
||||
|
||||
from time import sleep
|
||||
from itertools import chain
|
||||
from itertools import chain, groupby
|
||||
from math import ceil
|
||||
|
||||
from mininet.cli import CLI
|
||||
from mininet.log import info, error, debug, output
|
||||
from mininet.node import Host, OVSKernelSwitch, Controller
|
||||
from mininet.log import info, error, debug, output, warn
|
||||
from mininet.node import ( Node, Host, OVSKernelSwitch, DefaultController,
|
||||
Controller )
|
||||
from mininet.nodelib import NAT
|
||||
from mininet.link import Link, Intf
|
||||
from mininet.util import quietRun, errFail, fixLimits, numCores, ensureRoot
|
||||
from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
|
||||
from mininet.util import ( quietRun, fixLimits, numCores, ensureRoot,
|
||||
macColonHex, ipStr, ipParse, netParse, ipAdd,
|
||||
waitListening, BaseString )
|
||||
from mininet.term import cleanUpScreens, makeTerms
|
||||
|
||||
# Mininet version: should be consistent with README and LICENSE
|
||||
VERSION = "2.0.0"
|
||||
VERSION = "2.3.0d4"
|
||||
|
||||
class Mininet( object ):
|
||||
"Network emulation with hosts spawned in network namespaces."
|
||||
|
||||
def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
|
||||
controller=Controller, link=Link, intf=Intf,
|
||||
controller=DefaultController, link=Link, intf=Intf,
|
||||
build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
|
||||
inNamespace=False,
|
||||
autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
|
||||
listenPort=None ):
|
||||
listenPort=None, waitConnected=False ):
|
||||
"""Create Mininet object.
|
||||
topo: Topo (topology) object or None
|
||||
switch: default Switch class
|
||||
@@ -138,7 +144,9 @@ class Mininet( object ):
|
||||
self.intf = intf
|
||||
self.ipBase = ipBase
|
||||
self.ipBaseNum, self.prefixLen = netParse( self.ipBase )
|
||||
self.nextIP = 1 # start for address allocation
|
||||
hostIP = ( 0xffffffff >> self.prefixLen ) & self.ipBaseNum
|
||||
# Start for address allocation
|
||||
self.nextIP = hostIP if hostIP > 0 else 1
|
||||
self.inNamespace = inNamespace
|
||||
self.xterms = xterms
|
||||
self.cleanup = cleanup
|
||||
@@ -148,12 +156,13 @@ class Mininet( object ):
|
||||
self.numCores = numCores()
|
||||
self.nextCore = 0 # next core for pinning hosts to CPUs
|
||||
self.listenPort = listenPort
|
||||
self.waitConn = waitConnected
|
||||
|
||||
self.hosts = []
|
||||
self.switches = []
|
||||
self.controllers = []
|
||||
self.links = []
|
||||
|
||||
|
||||
self.nameToNode = {} # name to Node (Host/Switch) objects
|
||||
|
||||
self.terms = [] # list of spawned xterm processes
|
||||
@@ -164,6 +173,36 @@ class Mininet( object ):
|
||||
if topo and build:
|
||||
self.build()
|
||||
|
||||
def waitConnected( self, timeout=None, delay=.5 ):
|
||||
"""wait for each switch to connect to a controller,
|
||||
up to 5 seconds
|
||||
timeout: time to wait, or None to wait indefinitely
|
||||
delay: seconds to sleep per iteration
|
||||
returns: True if all switches are connected"""
|
||||
info( '*** Waiting for switches to connect\n' )
|
||||
time = 0
|
||||
remaining = list( self.switches )
|
||||
while True:
|
||||
for switch in tuple( remaining ):
|
||||
if switch.connected():
|
||||
info( '%s ' % switch )
|
||||
remaining.remove( switch )
|
||||
if not remaining:
|
||||
info( '\n' )
|
||||
return True
|
||||
if timeout is not None and time > timeout:
|
||||
break
|
||||
sleep( delay )
|
||||
time += delay
|
||||
warn( 'Timed out after %d seconds\n' % time )
|
||||
for switch in remaining:
|
||||
if not switch.connected():
|
||||
warn( 'Warning: %s is not connected to a controller\n'
|
||||
% switch.name )
|
||||
else:
|
||||
remaining.remove( switch )
|
||||
return not remaining
|
||||
|
||||
def addHost( self, name, cls=None, **params ):
|
||||
"""Add host.
|
||||
name: name of host to add
|
||||
@@ -176,7 +215,7 @@ class Mininet( object ):
|
||||
prefixLen=self.prefixLen ) +
|
||||
'/%s' % self.prefixLen }
|
||||
if self.autoSetMacs:
|
||||
defaults[ 'mac'] = macColonHex( self.nextIP )
|
||||
defaults[ 'mac' ] = macColonHex( self.nextIP )
|
||||
if self.autoPinCpus:
|
||||
defaults[ 'cores' ] = self.nextCore
|
||||
self.nextCore = ( self.nextCore + 1 ) % self.numCores
|
||||
@@ -189,6 +228,24 @@ class Mininet( object ):
|
||||
self.nameToNode[ name ] = h
|
||||
return h
|
||||
|
||||
def delNode( self, node, nodes=None):
|
||||
"""Delete node
|
||||
node: node to delete
|
||||
nodes: optional list to delete from (e.g. self.hosts)"""
|
||||
if nodes is None:
|
||||
nodes = ( self.hosts if node in self.hosts else
|
||||
( self.switches if node in self.switches else
|
||||
( self.controllers if node in self.controllers else
|
||||
[] ) ) )
|
||||
node.stop( deleteIntfs=True )
|
||||
node.terminate()
|
||||
nodes.remove( node )
|
||||
del self.nameToNode[ node.name ]
|
||||
|
||||
def delHost( self, host ):
|
||||
"Delete a host"
|
||||
self.delNode( host, nodes=self.hosts )
|
||||
|
||||
def addSwitch( self, name, cls=None, **params ):
|
||||
"""Add switch.
|
||||
name: name of switch to add
|
||||
@@ -207,17 +264,61 @@ class Mininet( object ):
|
||||
self.nameToNode[ name ] = sw
|
||||
return sw
|
||||
|
||||
def delSwitch( self, switch ):
|
||||
"Delete a switch"
|
||||
self.delNode( switch, nodes=self.switches )
|
||||
|
||||
def addController( self, name='c0', controller=None, **params ):
|
||||
"""Add controller.
|
||||
controller: Controller class"""
|
||||
# Get controller class
|
||||
if not controller:
|
||||
controller = self.controller
|
||||
controller_new = controller( name, **params )
|
||||
# Construct new controller if one is not given
|
||||
if isinstance( name, Controller ):
|
||||
controller_new = name
|
||||
# Pylint thinks controller is a str()
|
||||
# pylint: disable=maybe-no-member
|
||||
name = controller_new.name
|
||||
# pylint: enable=maybe-no-member
|
||||
else:
|
||||
controller_new = controller( name, **params )
|
||||
# Add new controller to net
|
||||
if controller_new: # allow controller-less setups
|
||||
self.controllers.append( controller_new )
|
||||
self.nameToNode[ name ] = controller_new
|
||||
return controller_new
|
||||
|
||||
def delController( self, controller ):
|
||||
"""Delete a controller
|
||||
Warning - does not reconfigure switches, so they
|
||||
may still attempt to connect to it!"""
|
||||
self.delNode( controller )
|
||||
|
||||
def addNAT( self, name='nat0', connect=True, inNamespace=False,
|
||||
**params):
|
||||
"""Add a NAT to the Mininet network
|
||||
name: name of NAT node
|
||||
connect: switch to connect to | True (s1) | None
|
||||
inNamespace: create in a network namespace
|
||||
params: other NAT node params, notably:
|
||||
ip: used as default gateway address"""
|
||||
nat = self.addHost( name, cls=NAT, inNamespace=inNamespace,
|
||||
subnet=self.ipBase, **params )
|
||||
# find first switch and create link
|
||||
if connect:
|
||||
if not isinstance( connect, Node ):
|
||||
# Use first switch if not specified
|
||||
connect = self.switches[ 0 ]
|
||||
# Connect the nat to the switch
|
||||
self.addLink( nat, connect )
|
||||
# Set the default route on hosts
|
||||
natIP = nat.params[ 'ip' ].split('/')[ 0 ]
|
||||
for host in self.hosts:
|
||||
if host.inNamespace:
|
||||
host.setDefaultRoute( 'via %s' % natIP )
|
||||
return nat
|
||||
|
||||
# BL: We now have four ways to look up nodes
|
||||
# This may (should?) be cleaned up in the future.
|
||||
def getNodeByName( self, *args ):
|
||||
@@ -231,39 +332,106 @@ class Mininet( object ):
|
||||
return self.getNodeByName( *args )
|
||||
|
||||
# Even more convenient syntax for node lookup and iteration
|
||||
def __getitem__( self, *args ):
|
||||
"""net [ name ] operator: Return node(s) with given name(s)"""
|
||||
return self.getNodeByName( *args )
|
||||
def __getitem__( self, key ):
|
||||
"net[ name ] operator: Return node with given name"
|
||||
return self.nameToNode[ key ]
|
||||
|
||||
def __delitem__( self, key ):
|
||||
"del net[ name ] operator - delete node with given name"
|
||||
self.delNode( self.nameToNode[ key ] )
|
||||
|
||||
def __iter__( self ):
|
||||
"return iterator over nodes"
|
||||
return chain( self.hosts, self.switches, self.controllers )
|
||||
"return iterator over node names"
|
||||
for node in chain( self.hosts, self.switches, self.controllers ):
|
||||
yield node.name
|
||||
|
||||
def __len__( self ):
|
||||
"returns number of nodes in net"
|
||||
return ( len( self.hosts ) + len( self.switches ) +
|
||||
len( self.controllers ) )
|
||||
|
||||
def __contains__( self, item ):
|
||||
"returns True if net contains named node"
|
||||
return item in self.nameToNode
|
||||
|
||||
def keys( self ):
|
||||
"return a list of all node names or net's keys"
|
||||
return list( self )
|
||||
|
||||
def values( self ):
|
||||
"return a list of all nodes or net's values"
|
||||
return [ self[name] for name in self ]
|
||||
|
||||
def items( self ):
|
||||
"return (key,value) tuple list for every node in net"
|
||||
return zip( self.keys(), self.values() )
|
||||
|
||||
@staticmethod
|
||||
def randMac():
|
||||
"Return a random, non-multicast MAC address"
|
||||
return macColonHex( random.randint(1, 2**48 - 1) & 0xfeffffffffff |
|
||||
0x020000000000 )
|
||||
|
||||
def addLink( self, node1, node2, port1=None, port2=None,
|
||||
cls=None, **params ):
|
||||
""""Add a link from node1 to node2
|
||||
node1: source node
|
||||
node2: dest node
|
||||
port1: source port
|
||||
port2: dest port
|
||||
node1: source node (or name)
|
||||
node2: dest node (or name)
|
||||
port1: source port (optional)
|
||||
port2: dest port (optional)
|
||||
cls: link class (optional)
|
||||
params: additional link params (optional)
|
||||
returns: link object"""
|
||||
defaults = { 'port1': port1,
|
||||
'port2': port2,
|
||||
'intf': self.intf }
|
||||
defaults.update( params )
|
||||
if not cls:
|
||||
cls = self.link
|
||||
link = cls( node1, node2, **defaults )
|
||||
# Accept node objects or names
|
||||
node1 = node1 if not isinstance( node1, BaseString ) else self[ node1 ]
|
||||
node2 = node2 if not isinstance( node2, BaseString ) else self[ node2 ]
|
||||
options = dict( params )
|
||||
# Port is optional
|
||||
if port1 is not None:
|
||||
options.setdefault( 'port1', port1 )
|
||||
if port2 is not None:
|
||||
options.setdefault( 'port2', port2 )
|
||||
if self.intf is not None:
|
||||
options.setdefault( 'intf', self.intf )
|
||||
# Set default MAC - this should probably be in Link
|
||||
options.setdefault( 'addr1', self.randMac() )
|
||||
options.setdefault( 'addr2', self.randMac() )
|
||||
cls = self.link if cls is None else cls
|
||||
link = cls( node1, node2, **options )
|
||||
self.links.append( link )
|
||||
return link
|
||||
|
||||
def delLink( self, link ):
|
||||
"Remove a link from this network"
|
||||
link.delete()
|
||||
self.links.remove( link )
|
||||
|
||||
def linksBetween( self, node1, node2 ):
|
||||
"Return Links between node1 and node2"
|
||||
return [ link for link in self.links
|
||||
if ( node1, node2 ) in (
|
||||
( link.intf1.node, link.intf2.node ),
|
||||
( link.intf2.node, link.intf1.node ) ) ]
|
||||
|
||||
def delLinkBetween( self, node1, node2, index=0, allLinks=False ):
|
||||
"""Delete link(s) between node1 and node2
|
||||
index: index of link to delete if multiple links (0)
|
||||
allLinks: ignore index and delete all such links (False)
|
||||
returns: deleted link(s)"""
|
||||
links = self.linksBetween( node1, node2 )
|
||||
if not allLinks:
|
||||
links = [ links[ index ] ]
|
||||
for link in links:
|
||||
self.delLink( link )
|
||||
return links
|
||||
|
||||
def configHosts( self ):
|
||||
"Configure a set of hosts."
|
||||
for host in self.hosts:
|
||||
info( host.name + ' ' )
|
||||
intf = host.defaultIntf()
|
||||
if intf:
|
||||
host.configDefault( defaultRoute=intf )
|
||||
host.configDefault()
|
||||
else:
|
||||
# Don't configure nonexistent intf
|
||||
host.configDefault( ip=None, mac=None )
|
||||
@@ -273,7 +441,6 @@ class Mininet( object ):
|
||||
# quietRun( 'renice +18 -p ' + repr( host.pid ) )
|
||||
# This may not be the right place to do this, but
|
||||
# it needs to be done somewhere.
|
||||
host.cmd( 'ifconfig lo up' )
|
||||
info( '\n' )
|
||||
|
||||
def buildFromTopo( self, topo=None ):
|
||||
@@ -288,14 +455,18 @@ class Mininet( object ):
|
||||
|
||||
info( '*** Creating network\n' )
|
||||
|
||||
if not self.controllers:
|
||||
if not self.controllers and self.controller:
|
||||
# Add a default controller
|
||||
info( '*** Adding controller\n' )
|
||||
classes = self.controller
|
||||
if type( classes ) is not list:
|
||||
if not isinstance( classes, list ):
|
||||
classes = [ classes ]
|
||||
for i, cls in enumerate( classes ):
|
||||
self.addController( 'c%d' % i, cls )
|
||||
# Allow Controller objects because nobody understands partial()
|
||||
if isinstance( cls, Controller ):
|
||||
self.addController( cls )
|
||||
else:
|
||||
self.addController( 'c%d' % i, cls )
|
||||
|
||||
info( '*** Adding hosts:\n' )
|
||||
for hostName in topo.hosts():
|
||||
@@ -304,16 +475,19 @@ class Mininet( object ):
|
||||
|
||||
info( '\n*** Adding switches:\n' )
|
||||
for switchName in topo.switches():
|
||||
self.addSwitch( switchName, **topo.nodeInfo( switchName) )
|
||||
# A bit ugly: add batch parameter if appropriate
|
||||
params = topo.nodeInfo( switchName)
|
||||
cls = params.get( 'cls', self.switch )
|
||||
if hasattr( cls, 'batchStartup' ):
|
||||
params.setdefault( 'batch', True )
|
||||
self.addSwitch( switchName, **params )
|
||||
info( switchName + ' ' )
|
||||
|
||||
info( '\n*** Adding links:\n' )
|
||||
for srcName, dstName in topo.links(sort=True):
|
||||
src, dst = self.nameToNode[ srcName ], self.nameToNode[ dstName ]
|
||||
params = topo.linkInfo( srcName, dstName )
|
||||
srcPort, dstPort = topo.port( srcName, dstName )
|
||||
self.addLink( src, dst, srcPort, dstPort, **params )
|
||||
info( '(%s, %s) ' % ( src.name, dst.name ) )
|
||||
for srcName, dstName, params in topo.links(
|
||||
sort=True, withInfo=True ):
|
||||
self.addLink( **params )
|
||||
info( '(%s, %s) ' % ( srcName, dstName ) )
|
||||
|
||||
info( '\n' )
|
||||
|
||||
@@ -326,7 +500,7 @@ class Mininet( object ):
|
||||
"Build mininet."
|
||||
if self.topo:
|
||||
self.buildFromTopo( self.topo )
|
||||
if ( self.inNamespace ):
|
||||
if self.inNamespace:
|
||||
self.configureControlNetwork()
|
||||
info( '*** Configuring hosts\n' )
|
||||
self.configHosts()
|
||||
@@ -338,6 +512,9 @@ class Mininet( object ):
|
||||
|
||||
def startTerms( self ):
|
||||
"Start a terminal for each node."
|
||||
if 'DISPLAY' not in os.environ:
|
||||
error( "Error starting terms: Cannot connect to display\n" )
|
||||
return
|
||||
info( "*** Running terms on %s\n" % os.environ[ 'DISPLAY' ] )
|
||||
cleanUpScreens()
|
||||
self.terms += makeTerms( self.controllers, 'controller' )
|
||||
@@ -363,46 +540,59 @@ class Mininet( object ):
|
||||
self.build()
|
||||
info( '*** Starting controller\n' )
|
||||
for controller in self.controllers:
|
||||
info( controller.name + ' ')
|
||||
controller.start()
|
||||
info( '\n' )
|
||||
info( '*** Starting %s switches\n' % len( self.switches ) )
|
||||
for switch in self.switches:
|
||||
info( switch.name + ' ')
|
||||
switch.start( self.controllers )
|
||||
started = {}
|
||||
for swclass, switches in groupby(
|
||||
sorted( self.switches,
|
||||
key=lambda s: str( type( s ) ) ), type ):
|
||||
switches = tuple( switches )
|
||||
if hasattr( swclass, 'batchStartup' ):
|
||||
success = swclass.batchStartup( switches )
|
||||
started.update( { s: s for s in success } )
|
||||
info( '\n' )
|
||||
if self.waitConn:
|
||||
self.waitConnected()
|
||||
|
||||
def stop( self ):
|
||||
"Stop the controller(s), switches and hosts"
|
||||
if self.terms:
|
||||
info( '*** Stopping %i terms\n' % len( self.terms ) )
|
||||
self.stopXterms()
|
||||
info( '*** Stopping %i controllers\n' % len( self.controllers ) )
|
||||
for controller in self.controllers:
|
||||
info( controller.name + ' ' )
|
||||
controller.stop()
|
||||
info( '\n' )
|
||||
info( '*** Stopping %i switches\n' % len( self.switches ) )
|
||||
for switch in self.switches:
|
||||
info( switch.name + ' ' )
|
||||
switch.stop( deleteIntfs=False )
|
||||
info( '\n' )
|
||||
info( '*** Removing links\n' )
|
||||
if self.terms:
|
||||
info( '*** Stopping %i terms\n' % len( self.terms ) )
|
||||
self.stopXterms()
|
||||
info( '*** Stopping %i links\n' % len( self.links ) )
|
||||
for link in self.links:
|
||||
info( '.' )
|
||||
link.delete()
|
||||
info( '\n*** Terminating switches\n' )
|
||||
link.stop()
|
||||
info( '\n' )
|
||||
info( '*** Stopping %i switches\n' % len( self.switches ) )
|
||||
stopped = {}
|
||||
for swclass, switches in groupby(
|
||||
sorted( self.switches,
|
||||
key=lambda s: str( type( s ) ) ), type ):
|
||||
switches = tuple( switches )
|
||||
if hasattr( swclass, 'batchShutdown' ):
|
||||
success = swclass.batchShutdown( switches )
|
||||
stopped.update( { s: s for s in success } )
|
||||
for switch in self.switches:
|
||||
info( '.' )
|
||||
info( switch.name + ' ' )
|
||||
if switch not in stopped:
|
||||
switch.stop()
|
||||
switch.terminate()
|
||||
info( '\n' )
|
||||
info( '*** Stopping %i hosts\n' % len( self.hosts ) )
|
||||
for host in self.hosts:
|
||||
info( host.name + ' ' )
|
||||
host.terminate()
|
||||
info( '\n' )
|
||||
nses = quietRun( 'ip netns list').strip().split()
|
||||
info( '*** Removing namespaces' )
|
||||
for ns in nses:
|
||||
errFail( 'ip netns del ' + ns )
|
||||
info( '\n*** Done\n' )
|
||||
|
||||
def run( self, test, *args, **kwargs ):
|
||||
@@ -422,13 +612,13 @@ class Mininet( object ):
|
||||
if hosts is None:
|
||||
hosts = self.hosts
|
||||
poller = select.poll()
|
||||
Node = hosts[ 0 ] # so we can call class method fdToNode
|
||||
h1 = hosts[ 0 ] # so we can call class method fdToNode
|
||||
for host in hosts:
|
||||
poller.register( host.stdout )
|
||||
while True:
|
||||
ready = poller.poll( timeoutms )
|
||||
for fd, event in ready:
|
||||
host = Node.fdToNode( fd )
|
||||
host = h1.fdToNode( fd )
|
||||
if event & select.POLLIN:
|
||||
line = host.readline()
|
||||
if line is not None:
|
||||
@@ -445,13 +635,13 @@ class Mininet( object ):
|
||||
"Parse ping output and return packets sent, received."
|
||||
# Check for downed link
|
||||
if 'connect: Network is unreachable' in pingOutput:
|
||||
return (1, 0)
|
||||
r = r'(\d+) packets transmitted, (\d+) received'
|
||||
return 1, 0
|
||||
r = r'(\d+) packets transmitted, (\d+)( packets)? received'
|
||||
m = re.search( r, pingOutput )
|
||||
if m is None:
|
||||
error( '*** Error: could not parse ping output: %s\n' %
|
||||
pingOutput )
|
||||
return (1, 0)
|
||||
return 1, 0
|
||||
sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
|
||||
return sent, received
|
||||
|
||||
@@ -474,8 +664,12 @@ class Mininet( object ):
|
||||
opts = ''
|
||||
if timeout:
|
||||
opts = '-W %s' % timeout
|
||||
result = node.cmd( 'ping -c1 %s %s' % (opts, dest.IP()) )
|
||||
sent, received = self._parsePing( result )
|
||||
if dest.intfs:
|
||||
result = node.cmd( 'ping -c1 %s %s' %
|
||||
(opts, dest.IP()) )
|
||||
sent, received = self._parsePing( result )
|
||||
else:
|
||||
sent, received = 0, 0
|
||||
packets += sent
|
||||
if received > sent:
|
||||
error( '*** Error: received too many packets' )
|
||||
@@ -485,27 +679,41 @@ class Mininet( object ):
|
||||
lost += sent - received
|
||||
output( ( '%s ' % dest.name ) if received else 'X ' )
|
||||
output( '\n' )
|
||||
ploss = 100 * lost / packets
|
||||
output( "*** Results: %i%% dropped (%d/%d lost)\n" %
|
||||
( ploss, lost, packets ) )
|
||||
if packets > 0:
|
||||
ploss = 100.0 * lost / packets
|
||||
received = packets - lost
|
||||
output( "*** Results: %i%% dropped (%d/%d received)\n" %
|
||||
( ploss, received, packets ) )
|
||||
else:
|
||||
ploss = 0
|
||||
output( "*** Warning: No packets sent\n" )
|
||||
return ploss
|
||||
|
||||
@staticmethod
|
||||
def _parsePingFull( pingOutput ):
|
||||
"Parse ping output and return all data."
|
||||
errorTuple = (1, 0, 0, 0, 0, 0)
|
||||
# Check for downed link
|
||||
if 'connect: Network is unreachable' in pingOutput:
|
||||
return (1, 0)
|
||||
r = r'(\d+) packets transmitted, (\d+) received'
|
||||
r = r'[uU]nreachable'
|
||||
m = re.search( r, pingOutput )
|
||||
if m is not None:
|
||||
return errorTuple
|
||||
r = r'(\d+) packets transmitted, (\d+)( packets)? received'
|
||||
m = re.search( r, pingOutput )
|
||||
if m is None:
|
||||
error( '*** Error: could not parse ping output: %s\n' %
|
||||
pingOutput )
|
||||
return (1, 0, 0, 0, 0, 0)
|
||||
return errorTuple
|
||||
sent, received = int( m.group( 1 ) ), int( m.group( 2 ) )
|
||||
r = r'rtt min/avg/max/mdev = '
|
||||
r += r'(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+) ms'
|
||||
m = re.search( r, pingOutput )
|
||||
if m is None:
|
||||
if received == 0:
|
||||
return errorTuple
|
||||
error( '*** Error: could not parse ping output: %s\n' %
|
||||
pingOutput )
|
||||
return errorTuple
|
||||
rttmin = float( m.group( 1 ) )
|
||||
rttavg = float( m.group( 2 ) )
|
||||
rttmax = float( m.group( 3 ) )
|
||||
@@ -545,10 +753,10 @@ class Mininet( object ):
|
||||
(rttmin, rttavg, rttmax, rttdev) )
|
||||
return all_outputs
|
||||
|
||||
def pingAll( self ):
|
||||
def pingAll( self, timeout=None ):
|
||||
"""Ping between all hosts.
|
||||
returns: ploss packet loss percentage"""
|
||||
return self.ping()
|
||||
return self.ping( timeout=timeout )
|
||||
|
||||
def pingPair( self ):
|
||||
"""Ping between first two hosts, useful for testing.
|
||||
@@ -583,41 +791,48 @@ class Mininet( object ):
|
||||
|
||||
# XXX This should be cleaned up
|
||||
|
||||
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
|
||||
def iperf( self, hosts=None, l4Type='TCP', udpBw='10M', fmt=None,
|
||||
seconds=5, port=5001):
|
||||
"""Run iperf between two hosts.
|
||||
hosts: list of hosts; if None, uses opposite hosts
|
||||
hosts: list of hosts; if None, uses first and last hosts
|
||||
l4Type: string, one of [ TCP, UDP ]
|
||||
returns: results two-element array of server and client speeds"""
|
||||
if not quietRun( 'which telnet' ):
|
||||
error( 'Cannot find telnet in $PATH - required for iperf test' )
|
||||
return
|
||||
if not hosts:
|
||||
hosts = [ self.hosts[ 0 ], self.hosts[ -1 ] ]
|
||||
else:
|
||||
assert len( hosts ) == 2
|
||||
udpBw: bandwidth target for UDP test
|
||||
fmt: iperf format argument if any
|
||||
seconds: iperf time to transmit
|
||||
port: iperf port
|
||||
returns: two-element array of [ server, client ] speeds
|
||||
note: send() is buffered, so client rate can be much higher than
|
||||
the actual transmission rate; on an unloaded system, server
|
||||
rate should be much closer to the actual receive rate"""
|
||||
hosts = hosts or [ self.hosts[ 0 ], self.hosts[ -1 ] ]
|
||||
assert len( hosts ) == 2
|
||||
client, server = hosts
|
||||
output( '*** Iperf: testing ' + l4Type + ' bandwidth between ' )
|
||||
output( "%s and %s\n" % ( client.name, server.name ) )
|
||||
output( '*** Iperf: testing', l4Type, 'bandwidth between',
|
||||
client, 'and', server, '\n' )
|
||||
server.cmd( 'killall -9 iperf' )
|
||||
iperfArgs = 'iperf '
|
||||
iperfArgs = 'iperf -p %d ' % port
|
||||
bwArgs = ''
|
||||
if l4Type == 'UDP':
|
||||
iperfArgs += '-u '
|
||||
bwArgs = '-b ' + udpBw + ' '
|
||||
elif l4Type != 'TCP':
|
||||
raise Exception( 'Unexpected l4 type: %s' % l4Type )
|
||||
server.sendCmd( iperfArgs + '-s', printPid=True )
|
||||
servout = ''
|
||||
while server.lastPid is None:
|
||||
servout += server.monitor()
|
||||
if fmt:
|
||||
iperfArgs += '-f %s ' % fmt
|
||||
server.sendCmd( iperfArgs + '-s' )
|
||||
if l4Type == 'TCP':
|
||||
while 'Connected' not in client.cmd(
|
||||
'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
|
||||
output('waiting for iperf to start up...')
|
||||
sleep(.5)
|
||||
cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
|
||||
bwArgs )
|
||||
if not waitListening( client, server.IP(), port ):
|
||||
raise Exception( 'Could not connect to iperf on port %d'
|
||||
% port )
|
||||
cliout = client.cmd( iperfArgs + '-t %d -c ' % seconds +
|
||||
server.IP() + ' ' + bwArgs )
|
||||
debug( 'Client output: %s\n' % cliout )
|
||||
servout = ''
|
||||
# We want the last *b/sec from the iperf server output
|
||||
# for TCP, there are two of them because of waitListening
|
||||
count = 2 if l4Type == 'TCP' else 1
|
||||
while len( re.findall( '/sec', servout ) ) < count:
|
||||
servout += server.monitor( timeoutms=5000 )
|
||||
server.sendInt()
|
||||
servout += server.waitOutput()
|
||||
debug( 'Server output: %s\n' % servout )
|
||||
@@ -630,36 +845,45 @@ class Mininet( object ):
|
||||
def runCpuLimitTest( self, cpu, duration=5 ):
|
||||
"""run CPU limit test with 'while true' processes.
|
||||
cpu: desired CPU fraction of each host
|
||||
duration: test duration in seconds
|
||||
duration: test duration in seconds (integer)
|
||||
returns a single list of measured CPU fractions as floats.
|
||||
"""
|
||||
pct = cpu * 100
|
||||
info('*** Testing CPU %.0f%% bandwidth limit\n' % pct)
|
||||
info( '*** Testing CPU %.0f%% bandwidth limit\n' % pct )
|
||||
hosts = self.hosts
|
||||
cores = int( quietRun( 'nproc' ) )
|
||||
# number of processes to run a while loop on per host
|
||||
num_procs = int( ceil( cores * cpu ) )
|
||||
pids = {}
|
||||
for h in hosts:
|
||||
h.cmd( 'while true; do a=1; done &' )
|
||||
pids = [h.cmd( 'echo $!' ).strip() for h in hosts]
|
||||
pids_str = ",".join(["%s" % pid for pid in pids])
|
||||
cmd = 'ps -p %s -o pid,%%cpu,args' % pids_str
|
||||
# It's a shame that this is what pylint prefers
|
||||
outputs = []
|
||||
pids[ h ] = []
|
||||
for _core in range( num_procs ):
|
||||
h.cmd( 'while true; do a=1; done &' )
|
||||
pids[ h ].append( h.cmd( 'echo $!' ).strip() )
|
||||
outputs = {}
|
||||
time = {}
|
||||
# get the initial cpu time for each host
|
||||
for host in hosts:
|
||||
outputs[ host ] = []
|
||||
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
|
||||
host, 'r' ) as f:
|
||||
time[ host ] = float( f.read() )
|
||||
for _ in range( duration ):
|
||||
sleep( 1 )
|
||||
outputs.append( quietRun( cmd ).strip() )
|
||||
for h in hosts:
|
||||
h.cmd( 'kill %1' )
|
||||
for host in hosts:
|
||||
with open( '/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' %
|
||||
host, 'r' ) as f:
|
||||
readTime = float( f.read() )
|
||||
outputs[ host ].append( ( ( readTime - time[ host ] )
|
||||
/ 1000000000 ) / cores * 100 )
|
||||
time[ host ] = readTime
|
||||
for h, pids in pids.items():
|
||||
for pid in pids:
|
||||
h.cmd( 'kill -9 %s' % pid )
|
||||
cpu_fractions = []
|
||||
for test_output in outputs:
|
||||
# Split by line. Ignore first line, which looks like this:
|
||||
# PID %CPU COMMAND\n
|
||||
for line in test_output.split('\n')[1:]:
|
||||
r = r'\d+\s*(\d+\.\d+)'
|
||||
m = re.search( r, line )
|
||||
if m is None:
|
||||
error( '*** Error: could not extract CPU fraction: %s\n' %
|
||||
line )
|
||||
return None
|
||||
cpu_fractions.append( float( m.group( 1 ) ) )
|
||||
for _host, outputs in outputs.items():
|
||||
for pct in outputs:
|
||||
cpu_fractions.append( pct )
|
||||
output( '*** Results: %s\n' % cpu_fractions )
|
||||
return cpu_fractions
|
||||
|
||||
@@ -675,10 +899,8 @@ class Mininet( object ):
|
||||
elif dst not in self.nameToNode:
|
||||
error( 'dst not in network: %s\n' % dst )
|
||||
else:
|
||||
if type( src ) is str:
|
||||
src = self.nameToNode[ src ]
|
||||
if type( dst ) is str:
|
||||
dst = self.nameToNode[ dst ]
|
||||
src = self.nameToNode[ src ]
|
||||
dst = self.nameToNode[ dst ]
|
||||
connections = src.connectionsTo( dst )
|
||||
if len( connections ) == 0:
|
||||
error( 'src and dst not connected: %s %s\n' % ( src, dst) )
|
||||
|
||||
+721
-260
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
Node Library for Mininet
|
||||
|
||||
This contains additional Node types which you may find to be useful.
|
||||
"""
|
||||
|
||||
from mininet.node import Node, Switch
|
||||
from mininet.log import info, warn
|
||||
from mininet.moduledeps import pathCheck
|
||||
from mininet.util import quietRun
|
||||
|
||||
|
||||
class LinuxBridge( Switch ):
|
||||
"Linux Bridge (with optional spanning tree)"
|
||||
|
||||
nextPrio = 100 # next bridge priority for spanning tree
|
||||
|
||||
def __init__( self, name, stp=False, prio=None, **kwargs ):
|
||||
"""stp: use spanning tree protocol? (default False)
|
||||
prio: optional explicit bridge priority for STP"""
|
||||
self.stp = stp
|
||||
if prio:
|
||||
self.prio = prio
|
||||
else:
|
||||
self.prio = LinuxBridge.nextPrio
|
||||
LinuxBridge.nextPrio += 1
|
||||
Switch.__init__( self, name, **kwargs )
|
||||
|
||||
def connected( self ):
|
||||
"Are we forwarding yet?"
|
||||
if self.stp:
|
||||
return 'forwarding' in self.cmd( 'brctl showstp', self )
|
||||
else:
|
||||
return True
|
||||
|
||||
def start( self, _controllers ):
|
||||
"Start Linux bridge"
|
||||
self.cmd( 'ifconfig', self, 'down' )
|
||||
self.cmd( 'brctl delbr', self )
|
||||
self.cmd( 'brctl addbr', self )
|
||||
if self.stp:
|
||||
self.cmd( 'brctl setbridgeprio', self.prio )
|
||||
self.cmd( 'brctl stp', self, 'on' )
|
||||
for i in self.intfList():
|
||||
if self.name in i.name:
|
||||
self.cmd( 'brctl addif', self, i )
|
||||
self.cmd( 'ifconfig', self, 'up' )
|
||||
|
||||
def stop( self, deleteIntfs=True ):
|
||||
"""Stop Linux bridge
|
||||
deleteIntfs: delete interfaces? (True)"""
|
||||
self.cmd( 'ifconfig', self, 'down' )
|
||||
self.cmd( 'brctl delbr', self )
|
||||
super( LinuxBridge, self ).stop( deleteIntfs )
|
||||
|
||||
def dpctl( self, *args ):
|
||||
"Run brctl command"
|
||||
return self.cmd( 'brctl', *args )
|
||||
|
||||
@classmethod
|
||||
def setup( cls ):
|
||||
"Check dependencies and warn about firewalling"
|
||||
pathCheck( 'brctl', moduleName='bridge-utils' )
|
||||
# Disable Linux bridge firewalling so that traffic can flow!
|
||||
for table in 'arp', 'ip', 'ip6':
|
||||
cmd = 'sysctl net.bridge.bridge-nf-call-%stables' % table
|
||||
out = quietRun( cmd ).strip()
|
||||
if out.endswith( '1' ):
|
||||
warn( 'Warning: Linux bridge may not work with', out, '\n' )
|
||||
|
||||
|
||||
class NAT( Node ):
|
||||
"NAT: Provides connectivity to external network"
|
||||
|
||||
def __init__( self, name, subnet='10.0/8',
|
||||
localIntf=None, flush=False, **params):
|
||||
"""Start NAT/forwarding between Mininet and external network
|
||||
subnet: Mininet subnet (default 10.0/8)
|
||||
flush: flush iptables before installing NAT rules"""
|
||||
super( NAT, self ).__init__( name, **params )
|
||||
|
||||
self.subnet = subnet
|
||||
self.localIntf = localIntf
|
||||
self.flush = flush
|
||||
self.forwardState = self.cmd( 'sysctl -n net.ipv4.ip_forward' ).strip()
|
||||
|
||||
def config( self, **params ):
|
||||
"""Configure the NAT and iptables"""
|
||||
super( NAT, self).config( **params )
|
||||
|
||||
if not self.localIntf:
|
||||
self.localIntf = self.defaultIntf()
|
||||
|
||||
if self.flush:
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=0' )
|
||||
self.cmd( 'iptables -F' )
|
||||
self.cmd( 'iptables -t nat -F' )
|
||||
# Create default entries for unmatched traffic
|
||||
self.cmd( 'iptables -P INPUT ACCEPT' )
|
||||
self.cmd( 'iptables -P OUTPUT ACCEPT' )
|
||||
self.cmd( 'iptables -P FORWARD DROP' )
|
||||
|
||||
# Install NAT rules
|
||||
self.cmd( 'iptables -I FORWARD',
|
||||
'-i', self.localIntf, '-d', self.subnet, '-j DROP' )
|
||||
self.cmd( 'iptables -A FORWARD',
|
||||
'-i', self.localIntf, '-s', self.subnet, '-j ACCEPT' )
|
||||
self.cmd( 'iptables -A FORWARD',
|
||||
'-o', self.localIntf, '-d', self.subnet, '-j ACCEPT' )
|
||||
self.cmd( 'iptables -t nat -A POSTROUTING',
|
||||
'-s', self.subnet, "'!'", '-d', self.subnet,
|
||||
'-j MASQUERADE' )
|
||||
|
||||
# Instruct the kernel to perform forwarding
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=1' )
|
||||
|
||||
# Prevent network-manager from messing with our interface
|
||||
# by specifying manual configuration in /etc/network/interfaces
|
||||
intf = self.localIntf
|
||||
cfile = '/etc/network/interfaces'
|
||||
line = '\niface %s inet manual\n' % intf
|
||||
config = open( cfile ).read()
|
||||
if ( line ) not in config:
|
||||
info( '*** Adding "' + line.strip() + '" to ' + cfile + '\n' )
|
||||
with open( cfile, 'a' ) as f:
|
||||
f.write( line )
|
||||
# Probably need to restart network-manager to be safe -
|
||||
# hopefully this won't disconnect you
|
||||
self.cmd( 'service network-manager restart' )
|
||||
|
||||
def terminate( self ):
|
||||
"Stop NAT/forwarding between Mininet and external network"
|
||||
# Remote NAT rules
|
||||
self.cmd( 'iptables -D FORWARD',
|
||||
'-i', self.localIntf, '-d', self.subnet, '-j DROP' )
|
||||
self.cmd( 'iptables -D FORWARD',
|
||||
'-i', self.localIntf, '-s', self.subnet, '-j ACCEPT' )
|
||||
self.cmd( 'iptables -D FORWARD',
|
||||
'-o', self.localIntf, '-d', self.subnet, '-j ACCEPT' )
|
||||
self.cmd( 'iptables -t nat -D POSTROUTING',
|
||||
'-s', self.subnet, '\'!\'', '-d', self.subnet,
|
||||
'-j MASQUERADE' )
|
||||
# Put the forwarding state back to what it was
|
||||
self.cmd( 'sysctl net.ipv4.ip_forward=%s' % self.forwardState )
|
||||
super( NAT, self ).terminate()
|
||||
+19
-5
@@ -15,8 +15,11 @@ def tunnelX11( node, display=None):
|
||||
"""Create an X11 tunnel from node:6000 to the root host
|
||||
display: display on root host (optional)
|
||||
returns: node $DISPLAY, Popen object for tunnel"""
|
||||
if display is None:
|
||||
if display is None and 'DISPLAY' in environ:
|
||||
display = environ[ 'DISPLAY' ]
|
||||
if display is None:
|
||||
error( "Error: Cannot connect to display\n" )
|
||||
return None, None
|
||||
host, screen = display.split( ':' )
|
||||
# Unix sockets should work
|
||||
if not host or host == 'unix':
|
||||
@@ -29,16 +32,16 @@ def tunnelX11( node, display=None):
|
||||
port = 6000 + int( float( screen ) )
|
||||
connection = r'TCP\:%s\:%s' % ( host, port )
|
||||
cmd = [ "socat", "TCP-LISTEN:%d,fork,reuseaddr" % port,
|
||||
"EXEC:'mnexec -a 1 socat STDIO %s'" % connection ]
|
||||
"EXEC:'mnexec -a 1 socat STDIO %s'" % connection ]
|
||||
return 'localhost:' + screen, node.popen( cmd )
|
||||
|
||||
def makeTerm( node, title='Node', term='xterm', display=None ):
|
||||
def makeTerm( node, title='Node', term='xterm', display=None, cmd='bash'):
|
||||
"""Create an X11 tunnel to the node and start up a terminal.
|
||||
node: Node object
|
||||
title: base title
|
||||
term: 'xterm' or 'gterm'
|
||||
returns: two Popen objects, tunnel and terminal"""
|
||||
title += ': ' + node.name
|
||||
title = '"%s: %s"' % ( title, node.name )
|
||||
if not node.inNamespace:
|
||||
title += ' (root)'
|
||||
cmds = {
|
||||
@@ -49,9 +52,20 @@ def makeTerm( node, title='Node', term='xterm', display=None ):
|
||||
error( 'invalid terminal type: %s' % term )
|
||||
return
|
||||
display, tunnel = tunnelX11( node, display )
|
||||
term = node.popen( cmds[ term ] + [ display, '-e', 'env TERM=ansi bash'] )
|
||||
if display is None:
|
||||
return []
|
||||
term = node.popen( cmds[ term ] +
|
||||
[ display, '-e', 'env TERM=ansi %s' % cmd ] )
|
||||
return [ tunnel, term ] if tunnel else [ term ]
|
||||
|
||||
def runX11( node, cmd ):
|
||||
"Run an X11 client on a node"
|
||||
_display, tunnel = tunnelX11( node )
|
||||
if _display is None:
|
||||
return []
|
||||
popen = node.popen( cmd )
|
||||
return [ tunnel, popen ]
|
||||
|
||||
def cleanUpScreens():
|
||||
"Remove moldy socat X11 tunnels."
|
||||
errRun( "pkill -9 -f mnexec.*socat" )
|
||||
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Run all mininet core tests
|
||||
-v : verbose output
|
||||
-quick : skip tests that take more than ~30 seconds
|
||||
"""
|
||||
|
||||
from unittest import defaultTestLoader, TextTestRunner
|
||||
import os
|
||||
import sys
|
||||
from mininet.util import ensureRoot
|
||||
from mininet.clean import cleanup
|
||||
from mininet.log import setLogLevel
|
||||
|
||||
def runTests( testDir, verbosity=1 ):
|
||||
"discover and run all tests in testDir"
|
||||
# ensure root and cleanup before starting tests
|
||||
ensureRoot()
|
||||
cleanup()
|
||||
# discover all tests in testDir
|
||||
testSuite = defaultTestLoader.discover( testDir )
|
||||
# run tests
|
||||
success = ( TextTestRunner( verbosity=verbosity )
|
||||
.run( testSuite ).wasSuccessful() )
|
||||
sys.exit( 0 if success else 1 )
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'warning' )
|
||||
# get the directory containing example tests
|
||||
thisdir = os.path.dirname( os.path.realpath( __file__ ) )
|
||||
vlevel = 2 if '-v' in sys.argv else 1
|
||||
runTests( testDir=thisdir, verbosity=vlevel )
|
||||
+169
-28
@@ -4,16 +4,18 @@
|
||||
Test creation and pings for topologies with link and/or CPU options."""
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import OVSKernelSwitch
|
||||
from mininet.node import OVSSwitch, UserSwitch, IVSSwitch
|
||||
from mininet.node import CPULimitedHost
|
||||
from mininet.link import TCLink
|
||||
from mininet.topo import Topo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.util import quietRun
|
||||
from mininet.clean import cleanup
|
||||
|
||||
|
||||
SWITCH = OVSKernelSwitch
|
||||
# Number of hosts for each test
|
||||
N = 2
|
||||
|
||||
@@ -31,24 +33,62 @@ class SingleSwitchOptionsTopo(Topo):
|
||||
host = self.addHost('h%s' % (h + 1))
|
||||
self.addLink(host, switch)
|
||||
|
||||
# Tell pylint not to complain about calls to other class
|
||||
# pylint: disable=E1101
|
||||
|
||||
class testOptionsTopo( unittest.TestCase ):
|
||||
"Verify ability to create networks with host and link options."
|
||||
class testOptionsTopoCommon( object ):
|
||||
"""Verify ability to create networks with host and link options
|
||||
(common code)."""
|
||||
|
||||
def runOptionsTopoTest( self, n, hopts=None, lopts=None ):
|
||||
switchClass = None # overridden in subclasses
|
||||
|
||||
@staticmethod
|
||||
def tearDown():
|
||||
"Clean up if necessary"
|
||||
if sys.exc_info != ( None, None, None ):
|
||||
cleanup()
|
||||
|
||||
def runOptionsTopoTest( self, n, msg, hopts=None, lopts=None ):
|
||||
"Generic topology-with-options test runner."
|
||||
mn = Mininet( topo=SingleSwitchOptionsTopo( n=n, hopts=hopts,
|
||||
lopts=lopts ),
|
||||
host=CPULimitedHost, link=TCLink )
|
||||
host=CPULimitedHost, link=TCLink,
|
||||
switch=self.switchClass, waitConnected=True )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
hoptsStr = ', '.join( '%s: %s' % ( opt, value )
|
||||
for opt, value in hopts.items() )
|
||||
loptsStr = ', '.join( '%s: %s' % ( opt, value )
|
||||
for opt, value in lopts.items() )
|
||||
msg += ( '%s%% of pings were dropped during mininet.ping().\n'
|
||||
'Topo = SingleSwitchTopo, %s hosts\n'
|
||||
'hopts = %s\n'
|
||||
'lopts = %s\n'
|
||||
'host = CPULimitedHost\n'
|
||||
'link = TCLink\n'
|
||||
'Switch = %s\n'
|
||||
% ( dropped, n, hoptsStr, loptsStr, self.switchClass ) )
|
||||
|
||||
def assertWithinTolerance(self, measured, expected, tolerance_frac):
|
||||
self.assertEqual( dropped, 0, msg=msg )
|
||||
|
||||
def assertWithinTolerance( self, measured, expected, tolerance_frac, msg ):
|
||||
"""Check that a given value is within a tolerance of expected
|
||||
tolerance_frac: less-than-1.0 value; 0.8 would yield 20% tolerance.
|
||||
"""
|
||||
self.assertTrue( float(measured) >= float(expected) * tolerance_frac )
|
||||
self.assertTrue( float(measured) >= float(expected) * tolerance_frac )
|
||||
upperBound = ( float( expected ) + ( 1 - tolerance_frac ) *
|
||||
float( expected ) )
|
||||
lowerBound = float( expected ) * tolerance_frac
|
||||
info = ( 'measured value is out of bounds\n'
|
||||
'expected value: %s\n'
|
||||
'measured value: %s\n'
|
||||
'failure tolerance: %s\n'
|
||||
'upper bound: %s\n'
|
||||
'lower bound: %s\n'
|
||||
% ( expected, measured, tolerance_frac,
|
||||
upperBound, lowerBound ) )
|
||||
msg += info
|
||||
|
||||
self.assertGreaterEqual( float( measured ), lowerBound, msg=msg )
|
||||
self.assertLessEqual( float( measured ), upperBound, msg=msg )
|
||||
|
||||
def testCPULimits( self ):
|
||||
"Verify topology creation with CPU limits set for both schedulers."
|
||||
@@ -58,46 +98,102 @@ class testOptionsTopo( unittest.TestCase ):
|
||||
#self.runOptionsTopoTest( N, hopts=hopts )
|
||||
|
||||
mn = Mininet( SingleSwitchOptionsTopo( n=N, hopts=hopts ),
|
||||
host=CPULimitedHost )
|
||||
host=CPULimitedHost, switch=self.switchClass,
|
||||
waitConnected=True )
|
||||
mn.start()
|
||||
results = mn.runCpuLimitTest( cpu=CPU_FRACTION )
|
||||
mn.stop()
|
||||
for cpu in results:
|
||||
self.assertWithinTolerance( cpu, CPU_FRACTION, CPU_TOLERANCE )
|
||||
hostUsage = '\n'.join( 'h%s: %s' %
|
||||
( n + 1,
|
||||
results[ (n - 1) * 5 : (n * 5) - 1 ] )
|
||||
for n in range( N ) )
|
||||
hoptsStr = ', '.join( '%s: %s' % ( opt, value )
|
||||
for opt, value in hopts.items() )
|
||||
msg = ( '\nTesting cpu limited to %d%% of cpu per host\n'
|
||||
'cpu usage percent per host:\n%s\n'
|
||||
'Topo = SingleSwitchTopo, %s hosts\n'
|
||||
'hopts = %s\n'
|
||||
'host = CPULimitedHost\n'
|
||||
'Switch = %s\n'
|
||||
% ( CPU_FRACTION * 100, hostUsage, N, hoptsStr,
|
||||
self.switchClass ) )
|
||||
for pct in results:
|
||||
#divide cpu by 100 to convert from percentage to fraction
|
||||
self.assertWithinTolerance( pct/100, CPU_FRACTION,
|
||||
CPU_TOLERANCE, msg )
|
||||
|
||||
def testLinkBandwidth( self ):
|
||||
"Verify that link bandwidths are accurate within a bound."
|
||||
if self.switchClass is UserSwitch:
|
||||
self.skipTest( 'UserSwitch has very poor performance -'
|
||||
' skipping for now' )
|
||||
BW = 5 # Mbps
|
||||
BW_TOLERANCE = 0.8 # BW fraction below which test should fail
|
||||
# Verify ability to create limited-link topo first;
|
||||
lopts = { 'bw': BW, 'use_htb': True }
|
||||
# Also verify correctness of limit limitng within a bound.
|
||||
mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
|
||||
link=TCLink )
|
||||
bw_strs = mn.run( mn.iperf )
|
||||
for bw_str in bw_strs:
|
||||
bw = float( bw_str.split(' ')[0] )
|
||||
self.assertWithinTolerance( bw, BW, BW_TOLERANCE )
|
||||
link=TCLink, switch=self.switchClass,
|
||||
waitConnected=True )
|
||||
bw_strs = mn.run( mn.iperf, fmt='m' )
|
||||
loptsStr = ', '.join( '%s: %s' % ( opt, value )
|
||||
for opt, value in lopts.items() )
|
||||
msg = ( '\nTesting link bandwidth limited to %d Mbps per link\n'
|
||||
'iperf results[ client, server ]: %s\n'
|
||||
'Topo = SingleSwitchTopo, %s hosts\n'
|
||||
'Link = TCLink\n'
|
||||
'lopts = %s\n'
|
||||
'host = default\n'
|
||||
'switch = %s\n'
|
||||
% ( BW, bw_strs, N, loptsStr, self.switchClass ) )
|
||||
|
||||
# On the client side, iperf doesn't wait for ACKs - it simply
|
||||
# reports how long it took to fill up the TCP send buffer.
|
||||
# As long as the kernel doesn't wait a long time before
|
||||
# delivering bytes to the iperf server, its reported data rate
|
||||
# should be close to the actual receive rate.
|
||||
serverRate, _clientRate = bw_strs
|
||||
bw = float( serverRate.split(' ')[0] )
|
||||
self.assertWithinTolerance( bw, BW, BW_TOLERANCE, msg )
|
||||
|
||||
def testLinkDelay( self ):
|
||||
"Verify that link delays are accurate within a bound."
|
||||
DELAY_MS = 15
|
||||
DELAY_TOLERANCE = 0.8 # Delay fraction below which test should fail
|
||||
REPS = 3
|
||||
lopts = { 'delay': '%sms' % DELAY_MS, 'use_htb': True }
|
||||
mn = Mininet( SingleSwitchOptionsTopo( n=N, lopts=lopts ),
|
||||
link=TCLink )
|
||||
ping_delays = mn.run( mn.pingFull )
|
||||
link=TCLink, switch=self.switchClass, autoStaticArp=True,
|
||||
waitConnected=True )
|
||||
mn.start()
|
||||
for _ in range( REPS ):
|
||||
ping_delays = mn.pingFull()
|
||||
mn.stop()
|
||||
test_outputs = ping_delays[0]
|
||||
# Ignore unused variables below
|
||||
# pylint: disable-msg=W0612
|
||||
# pylint: disable=W0612
|
||||
node, dest, ping_outputs = test_outputs
|
||||
sent, received, rttmin, rttavg, rttmax, rttdev = ping_outputs
|
||||
self.assertEqual( sent, received )
|
||||
# pylint: enable-msg=W0612
|
||||
pingFailMsg = 'sent %s pings, only received %s' % ( sent, received )
|
||||
self.assertEqual( sent, received, msg=pingFailMsg )
|
||||
# pylint: enable=W0612
|
||||
loptsStr = ', '.join( '%s: %s' % ( opt, value )
|
||||
for opt, value in lopts.items() )
|
||||
msg = ( '\nTesting Link Delay of %s ms\n'
|
||||
'ping results across 4 links:\n'
|
||||
'(Sent, Received, rttmin, rttavg, rttmax, rttdev)\n'
|
||||
'%s\n'
|
||||
'Topo = SingleSwitchTopo, %s hosts\n'
|
||||
'Link = TCLink\n'
|
||||
'lopts = %s\n'
|
||||
'host = default'
|
||||
'switch = %s\n'
|
||||
% ( DELAY_MS, ping_outputs, N, loptsStr, self.switchClass ) )
|
||||
|
||||
for rttval in [rttmin, rttavg, rttmax]:
|
||||
# Multiply delay by 4 to cover there & back on two links
|
||||
self.assertWithinTolerance( rttval, DELAY_MS * 4.0,
|
||||
DELAY_TOLERANCE)
|
||||
DELAY_TOLERANCE, msg )
|
||||
|
||||
def testLinkLoss( self ):
|
||||
"Verify that we see packet drops with a high configured loss rate."
|
||||
@@ -105,7 +201,9 @@ class testOptionsTopo( unittest.TestCase ):
|
||||
REPS = 1
|
||||
lopts = { 'loss': LOSS_PERCENT, 'use_htb': True }
|
||||
mn = Mininet( topo=SingleSwitchOptionsTopo( n=N, lopts=lopts ),
|
||||
host=CPULimitedHost, link=TCLink )
|
||||
host=CPULimitedHost, link=TCLink,
|
||||
switch=self.switchClass,
|
||||
waitConnected=True )
|
||||
# Drops are probabilistic, but the chance of no dropped packets is
|
||||
# 1 in 100 million with 4 hops for a link w/99% loss.
|
||||
dropped_total = 0
|
||||
@@ -113,14 +211,57 @@ class testOptionsTopo( unittest.TestCase ):
|
||||
for _ in range(REPS):
|
||||
dropped_total += mn.ping(timeout='1')
|
||||
mn.stop()
|
||||
self.assertTrue(dropped_total > 0)
|
||||
|
||||
loptsStr = ', '.join( '%s: %s' % ( opt, value )
|
||||
for opt, value in lopts.items() )
|
||||
msg = ( '\nTesting packet loss with %d%% loss rate\n'
|
||||
'number of dropped pings during mininet.ping(): %s\n'
|
||||
'expected number of dropped packets: 1\n'
|
||||
'Topo = SingleSwitchTopo, %s hosts\n'
|
||||
'Link = TCLink\n'
|
||||
'lopts = %s\n'
|
||||
'host = default\n'
|
||||
'switch = %s\n'
|
||||
% ( LOSS_PERCENT, dropped_total, N, loptsStr,
|
||||
self.switchClass ) )
|
||||
|
||||
self.assertGreater( dropped_total, 0, msg )
|
||||
|
||||
def testMostOptions( self ):
|
||||
"Verify topology creation with most link options and CPU limits."
|
||||
lopts = { 'bw': 10, 'delay': '5ms', 'use_htb': True }
|
||||
hopts = { 'cpu': 0.5 / N }
|
||||
self.runOptionsTopoTest( N, hopts=hopts, lopts=lopts )
|
||||
msg = '\nTesting many cpu and link options\n'
|
||||
self.runOptionsTopoTest( N, msg, hopts=hopts, lopts=lopts )
|
||||
|
||||
# pylint: enable=E1101
|
||||
|
||||
class testOptionsTopoOVSKernel( testOptionsTopoCommon, unittest.TestCase ):
|
||||
"""Verify ability to create networks with host and link options
|
||||
(OVS kernel switch)."""
|
||||
longMessage = True
|
||||
switchClass = OVSSwitch
|
||||
|
||||
@unittest.skip( 'Skipping OVS user switch test for now' )
|
||||
class testOptionsTopoOVSUser( testOptionsTopoCommon, unittest.TestCase ):
|
||||
"""Verify ability to create networks with host and link options
|
||||
(OVS user switch)."""
|
||||
longMessage = True
|
||||
switchClass = partial( OVSSwitch, datapath='user' )
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ivs-ctl' ), 'IVS is not installed' )
|
||||
class testOptionsTopoIVS( testOptionsTopoCommon, unittest.TestCase ):
|
||||
"Verify ability to create networks with host and link options (IVS)."
|
||||
longMessage = True
|
||||
switchClass = IVSSwitch
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ofprotocol' ),
|
||||
'Reference user switch is not installed' )
|
||||
class testOptionsTopoUserspace( testOptionsTopoCommon, unittest.TestCase ):
|
||||
"""Verify ability to create networks with host and link options
|
||||
(UserSwitch)."""
|
||||
longMessage = True
|
||||
switchClass = UserSwitch
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'warning' )
|
||||
|
||||
+81
-23
@@ -4,45 +4,103 @@
|
||||
Test creation and all-pairs ping for each included mininet topo type."""
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Host, Controller
|
||||
from mininet.node import UserSwitch, OVSKernelSwitch
|
||||
from mininet.node import UserSwitch, OVSSwitch, IVSSwitch
|
||||
from mininet.topo import SingleSwitchTopo, LinearTopo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.util import quietRun
|
||||
from mininet.clean import cleanup
|
||||
|
||||
SWITCHES = { 'user': UserSwitch,
|
||||
'ovsk': OVSKernelSwitch,
|
||||
}
|
||||
# Tell pylint not to complain about calls to other class
|
||||
# pylint: disable=E1101
|
||||
|
||||
class testSingleSwitchCommon( object ):
|
||||
"Test ping with single switch topology (common code)."
|
||||
|
||||
class testSingleSwitch( unittest.TestCase ):
|
||||
"For each datapath type, test ping with single switch topologies."
|
||||
switchClass = None # overridden in subclasses
|
||||
|
||||
@staticmethod
|
||||
def tearDown():
|
||||
"Clean up if necessary"
|
||||
if sys.exc_info != ( None, None, None ):
|
||||
cleanup()
|
||||
|
||||
def testMinimal( self ):
|
||||
"Ping test with both datapaths on minimal topology"
|
||||
for switch in SWITCHES.values():
|
||||
mn = Mininet( SingleSwitchTopo(), switch, Host, Controller )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
"Ping test on minimal topology"
|
||||
mn = Mininet( SingleSwitchTopo(), self.switchClass, Host, Controller,
|
||||
waitConnected=True )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
|
||||
def testSingle5( self ):
|
||||
"Ping test with both datapaths on 5-host single-switch topology"
|
||||
for switch in SWITCHES.values():
|
||||
mn = Mininet( SingleSwitchTopo( k=5 ), switch, Host, Controller )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
"Ping test on 5-host single-switch topology"
|
||||
mn = Mininet( SingleSwitchTopo( k=5 ), self.switchClass, Host,
|
||||
Controller, waitConnected=True )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
|
||||
# pylint: enable=E1101
|
||||
|
||||
class testSingleSwitchOVSKernel( testSingleSwitchCommon, unittest.TestCase ):
|
||||
"Test ping with single switch topology (OVS kernel switch)."
|
||||
switchClass = OVSSwitch
|
||||
|
||||
class testSingleSwitchOVSUser( testSingleSwitchCommon, unittest.TestCase ):
|
||||
"Test ping with single switch topology (OVS user switch)."
|
||||
switchClass = partial( OVSSwitch, datapath='user' )
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ivs-ctl' ), 'IVS is not installed' )
|
||||
class testSingleSwitchIVS( testSingleSwitchCommon, unittest.TestCase ):
|
||||
"Test ping with single switch topology (IVS switch)."
|
||||
switchClass = IVSSwitch
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ofprotocol' ),
|
||||
'Reference user switch is not installed' )
|
||||
class testSingleSwitchUserspace( testSingleSwitchCommon, unittest.TestCase ):
|
||||
"Test ping with single switch topology (Userspace switch)."
|
||||
switchClass = UserSwitch
|
||||
|
||||
|
||||
class testLinear( unittest.TestCase ):
|
||||
"For each datapath type, test all-pairs ping with LinearNet."
|
||||
# Tell pylint not to complain about calls to other class
|
||||
# pylint: disable=E1101
|
||||
|
||||
class testLinearCommon( object ):
|
||||
"Test all-pairs ping with LinearNet (common code)."
|
||||
|
||||
switchClass = None # overridden in subclasses
|
||||
|
||||
def testLinear5( self ):
|
||||
"Ping test with both datapaths on a 5-switch topology"
|
||||
for switch in SWITCHES.values():
|
||||
mn = Mininet( LinearTopo( k=5 ), switch, Host, Controller )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
"Ping test on a 5-switch topology"
|
||||
mn = Mininet( LinearTopo( k=5 ), self.switchClass, Host,
|
||||
Controller, waitConnected=True )
|
||||
dropped = mn.run( mn.ping )
|
||||
self.assertEqual( dropped, 0 )
|
||||
|
||||
# pylint: enable=E1101
|
||||
|
||||
|
||||
class testLinearOVSKernel( testLinearCommon, unittest.TestCase ):
|
||||
"Test all-pairs ping with LinearNet (OVS kernel switch)."
|
||||
switchClass = OVSSwitch
|
||||
|
||||
class testLinearOVSUser( testLinearCommon, unittest.TestCase ):
|
||||
"Test all-pairs ping with LinearNet (OVS user switch)."
|
||||
switchClass = partial( OVSSwitch, datapath='user' )
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ivs-ctl' ), 'IVS is not installed' )
|
||||
class testLinearIVS( testLinearCommon, unittest.TestCase ):
|
||||
"Test all-pairs ping with LinearNet (IVS switch)."
|
||||
switchClass = IVSSwitch
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ofprotocol' ),
|
||||
'Reference user switch is not installed' )
|
||||
class testLinearUserspace( testLinearCommon, unittest.TestCase ):
|
||||
"Test all-pairs ping with LinearNet (Userspace switch)."
|
||||
switchClass = UserSwitch
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Package: mininet
|
||||
Regression tests for switch dpid assignment."""
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
from mininet.net import Mininet
|
||||
from mininet.node import Host, Controller
|
||||
from mininet.node import ( UserSwitch, OVSSwitch, IVSSwitch )
|
||||
from mininet.topo import Topo
|
||||
from mininet.log import setLogLevel
|
||||
from mininet.util import quietRun
|
||||
from mininet.clean import cleanup
|
||||
|
||||
|
||||
class TestSwitchDpidAssignmentOVS( unittest.TestCase ):
|
||||
"Verify Switch dpid assignment."
|
||||
|
||||
switchClass = OVSSwitch # overridden in subclasses
|
||||
|
||||
def tearDown( self ):
|
||||
"Clean up if necessary"
|
||||
# satisfy pylint
|
||||
assert self
|
||||
if sys.exc_info != ( None, None, None ):
|
||||
cleanup()
|
||||
|
||||
def testDefaultDpid( self ):
|
||||
"""Verify that the default dpid is assigned using a valid provided
|
||||
canonical switchname if no dpid is passed in switch creation."""
|
||||
net = Mininet( Topo(), self.switchClass, Host, Controller )
|
||||
switch = net.addSwitch( 's1' )
|
||||
self.assertEqual( switch.defaultDpid(), switch.dpid )
|
||||
net.stop()
|
||||
|
||||
def dpidFrom( self, num ):
|
||||
"Compute default dpid from number"
|
||||
fmt = ( '%0' + str( self.switchClass.dpidLen ) + 'x' )
|
||||
return fmt % num
|
||||
|
||||
def testActualDpidAssignment( self ):
|
||||
"""Verify that Switch dpid is the actual dpid assigned if dpid is
|
||||
passed in switch creation."""
|
||||
dpid = self.dpidFrom( 0xABCD )
|
||||
net = Mininet( Topo(), self.switchClass, Host, Controller )
|
||||
switch = net.addSwitch( 's1', dpid=dpid )
|
||||
self.assertEqual( switch.dpid, dpid )
|
||||
net.stop()
|
||||
|
||||
def testDefaultDpidAssignmentFailure( self ):
|
||||
"""Verify that Default dpid assignment raises an Exception if the
|
||||
name of the switch does not contin a digit. Also verify the
|
||||
exception message."""
|
||||
net = Mininet( Topo(), self.switchClass, Host, Controller )
|
||||
with self.assertRaises( Exception ) as raises_cm:
|
||||
net.addSwitch( 'A' )
|
||||
self.assertTrue( 'Unable to derive '
|
||||
'default datapath ID - please either specify a dpid '
|
||||
'or use a canonical switch name such as s23.'
|
||||
in str( raises_cm.exception ) )
|
||||
net.stop()
|
||||
|
||||
def testDefaultDpidLen( self ):
|
||||
"""Verify that Default dpid length is 16 characters consisting of
|
||||
16 - len(hex of first string of contiguous digits passed in switch
|
||||
name) 0's followed by hex of first string of contiguous digits passed
|
||||
in switch name."""
|
||||
net = Mininet( Topo(), self.switchClass, Host, Controller )
|
||||
switch = net.addSwitch( 's123' )
|
||||
self.assertEqual( switch.dpid, self.dpidFrom( 123 ) )
|
||||
net.stop()
|
||||
|
||||
|
||||
class OVSUser( OVSSwitch):
|
||||
"OVS User Switch convenience class"
|
||||
def __init__( self, *args, **kwargs ):
|
||||
kwargs.update( datapath='user' )
|
||||
OVSSwitch.__init__( self, *args, **kwargs )
|
||||
|
||||
class testSwitchOVSUser( TestSwitchDpidAssignmentOVS ):
|
||||
"Test dpid assignnment of OVS User Switch."
|
||||
switchClass = OVSUser
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ivs-ctl' ),
|
||||
'IVS switch is not installed' )
|
||||
class testSwitchIVS( TestSwitchDpidAssignmentOVS ):
|
||||
"Test dpid assignment of IVS switch."
|
||||
switchClass = IVSSwitch
|
||||
|
||||
@unittest.skipUnless( quietRun( 'which ofprotocol' ),
|
||||
'Reference user switch is not installed' )
|
||||
class testSwitchUserspace( TestSwitchDpidAssignmentOVS ):
|
||||
"Test dpid assignment of Userspace switch."
|
||||
switchClass = UserSwitch
|
||||
|
||||
if __name__ == '__main__':
|
||||
setLogLevel( 'warning' )
|
||||
unittest.main()
|
||||
cleanup()
|
||||
Executable
+380
@@ -0,0 +1,380 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Tests for the Mininet Walkthrough
|
||||
|
||||
TODO: missing xterm test
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import re
|
||||
from mininet.util import quietRun, pexpect
|
||||
from distutils.version import StrictVersion
|
||||
from time import sleep
|
||||
|
||||
|
||||
def tsharkVersion():
|
||||
"Return tshark version"
|
||||
versionStr = quietRun( 'tshark -v' )
|
||||
versionMatch = re.findall( r'TShark[^\d]*(\d+.\d+.\d+)', versionStr )
|
||||
return versionMatch[ 0 ]
|
||||
|
||||
# pylint doesn't understand pexpect.match, unfortunately!
|
||||
# pylint:disable=maybe-no-member
|
||||
|
||||
class testWalkthrough( unittest.TestCase ):
|
||||
"Test Mininet walkthrough"
|
||||
|
||||
prompt = 'mininet>'
|
||||
|
||||
# PART 1
|
||||
def testHelp( self ):
|
||||
"Check the usage message"
|
||||
p = pexpect.spawn( 'mn -h' )
|
||||
index = p.expect( [ 'Usage: mn', pexpect.EOF ] )
|
||||
self.assertEqual( index, 0 )
|
||||
|
||||
def testWireshark( self ):
|
||||
"Use tshark to test the of dissector"
|
||||
# Satisfy pylint
|
||||
assert self
|
||||
if StrictVersion( tsharkVersion() ) < StrictVersion( '1.12.0' ):
|
||||
tshark = pexpect.spawn( 'tshark -i lo -R of' )
|
||||
else:
|
||||
tshark = pexpect.spawn( 'tshark -i lo -Y openflow_v1' )
|
||||
tshark.expect( [ 'Capturing on lo', "Capturing on 'Loopback'" ] )
|
||||
mn = pexpect.spawn( 'mn --test pingall' )
|
||||
mn.expect( '0% dropped' )
|
||||
tshark.expect( [ '74 Hello', '74 of_hello', '74 Type: OFPT_HELLO' ] )
|
||||
tshark.sendintr()
|
||||
mn.expect( pexpect.EOF )
|
||||
tshark.expect( pexpect.EOF )
|
||||
|
||||
def testBasic( self ):
|
||||
"Test basic CLI commands (help, nodes, net, dump)"
|
||||
p = pexpect.spawn( 'mn' )
|
||||
p.expect( self.prompt )
|
||||
# help command
|
||||
p.sendline( 'help' )
|
||||
index = p.expect( [ 'commands', self.prompt ] )
|
||||
self.assertEqual( index, 0, 'No output for "help" command')
|
||||
# nodes command
|
||||
p.sendline( 'nodes' )
|
||||
p.expect( r'([chs]\d ?){4}' )
|
||||
nodes = p.match.group( 0 ).split()
|
||||
self.assertEqual( len( nodes ), 4, 'No nodes in "nodes" command')
|
||||
p.expect( self.prompt )
|
||||
# net command
|
||||
p.sendline( 'net' )
|
||||
expected = [ x for x in nodes ]
|
||||
while len( expected ) > 0:
|
||||
index = p.expect( expected )
|
||||
node = p.match.group( 0 )
|
||||
expected.remove( node )
|
||||
p.expect( '\n' )
|
||||
self.assertEqual( len( expected ), 0, '"nodes" and "net" differ')
|
||||
p.expect( self.prompt )
|
||||
# dump command
|
||||
p.sendline( 'dump' )
|
||||
expected = [ r'<\w+ (%s)' % n for n in nodes ]
|
||||
actual = []
|
||||
for _ in nodes:
|
||||
index = p.expect( expected )
|
||||
node = p.match.group( 1 )
|
||||
actual.append( node )
|
||||
p.expect( '\n' )
|
||||
self.assertEqual( actual.sort(), nodes.sort(),
|
||||
'"nodes" and "dump" differ' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
def testHostCommands( self ):
|
||||
"Test ifconfig and ps on h1 and s1"
|
||||
p = pexpect.spawn( 'mn' )
|
||||
p.expect( self.prompt )
|
||||
# Third pattern is a local interface beginning with 'eth' or 'en'
|
||||
interfaces = [ r'h1-eth0[:\s]', r's1-eth1[:\s]',
|
||||
r'[^-](eth|en)\w*\d[:\s]', r'lo[:\s]',
|
||||
self.prompt ]
|
||||
# h1 ifconfig
|
||||
p.sendline( 'h1 ifconfig -a' )
|
||||
ifcount = 0
|
||||
while True:
|
||||
index = p.expect( interfaces )
|
||||
if index == 0 or index == 3:
|
||||
ifcount += 1
|
||||
elif index == 1:
|
||||
self.fail( 's1 interface displayed in "h1 ifconfig"' )
|
||||
elif index == 2:
|
||||
self.fail( 'eth0 displayed in "h1 ifconfig"' )
|
||||
else:
|
||||
break
|
||||
self.assertEqual( ifcount, 2, 'Missing interfaces on h1')
|
||||
# s1 ifconfig
|
||||
p.sendline( 's1 ifconfig -a' )
|
||||
ifcount = 0
|
||||
while True:
|
||||
index = p.expect( interfaces )
|
||||
if index == 0:
|
||||
self.fail( 'h1 interface displayed in "s1 ifconfig"' )
|
||||
elif index == 1 or index == 2 or index == 3:
|
||||
ifcount += 1
|
||||
else:
|
||||
break
|
||||
self.assertTrue( ifcount <= 3, 'Missing interfaces on s1')
|
||||
# h1 ps
|
||||
p.sendline( "h1 ps -a | egrep -v 'ps|grep'" )
|
||||
p.expect( self.prompt )
|
||||
h1Output = p.before
|
||||
# s1 ps
|
||||
p.sendline( "s1 ps -a | egrep -v 'ps|grep'" )
|
||||
p.expect( self.prompt )
|
||||
s1Output = p.before
|
||||
# strip command from ps output and compute diffs
|
||||
h1Output = h1Output.split( '\n' )[ 1: ]
|
||||
s1Output = s1Output.split( '\n' )[ 1: ]
|
||||
diffs = set( h1Output ).difference( set( s1Output ) )
|
||||
# allow up to two diffs to account for daemons, etc.
|
||||
self.assertTrue( len( diffs ) <= 2,
|
||||
'h1 and s1 "ps" output differ too much: %s' % diffs )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
def testConnectivity( self ):
|
||||
"Test ping and pingall"
|
||||
p = pexpect.spawn( 'mn' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 ping -c 1 h2' )
|
||||
p.expect( '1 packets transmitted, 1 received' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'pingall' )
|
||||
p.expect( '0% dropped' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
def testSimpleHTTP( self ):
|
||||
"Start an HTTP server on h1 and wget from h2"
|
||||
if 'Python 2' in quietRun( 'python --version' ):
|
||||
httpserver = 'SimpleHTTPServer'
|
||||
else:
|
||||
httpserver = 'http.server'
|
||||
p = pexpect.spawn( 'mn' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 python -m %s 80 &' % httpserver )
|
||||
# The walkthrough doesn't specify a delay here, and
|
||||
# we also don't read the output (also a possible problem),
|
||||
# but for now let's wait a couple of seconds to make
|
||||
# it less likely to fail due to the race condition.
|
||||
sleep( 2 )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( ' h2 wget -O - h1' )
|
||||
p.expect( '200 OK' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 kill %python' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
# PART 2
|
||||
def testRegressionRun( self ):
|
||||
"Test pingpair (0% drop) and iperf (bw > 0) regression tests"
|
||||
# test pingpair
|
||||
p = pexpect.spawn( 'mn --test pingpair' )
|
||||
p.expect( '0% dropped' )
|
||||
p.expect( pexpect.EOF )
|
||||
# test iperf
|
||||
p = pexpect.spawn( 'mn --test iperf' )
|
||||
p.expect( r"Results: \['([\d\.]+) .bits/sec'," )
|
||||
bw = float( p.match.group( 1 ) )
|
||||
self.assertTrue( bw > 0 )
|
||||
p.expect( pexpect.EOF )
|
||||
|
||||
def testTopoChange( self ):
|
||||
"Test pingall on single,3 and linear,4 topos"
|
||||
# testing single,3
|
||||
p = pexpect.spawn( 'mn --test pingall --topo single,3' )
|
||||
p.expect( r'(\d+)/(\d+) received')
|
||||
received = int( p.match.group( 1 ) )
|
||||
sent = int( p.match.group( 2 ) )
|
||||
self.assertEqual( sent, 6, 'Wrong number of pings sent in single,3' )
|
||||
self.assertEqual( sent, received, 'Dropped packets in single,3')
|
||||
p.expect( pexpect.EOF )
|
||||
# testing linear,4
|
||||
p = pexpect.spawn( 'mn --test pingall --topo linear,4' )
|
||||
p.expect( r'(\d+)/(\d+) received')
|
||||
received = int( p.match.group( 1 ) )
|
||||
sent = int( p.match.group( 2 ) )
|
||||
self.assertEqual( sent, 12, 'Wrong number of pings sent in linear,4' )
|
||||
self.assertEqual( sent, received, 'Dropped packets in linear,4')
|
||||
p.expect( pexpect.EOF )
|
||||
|
||||
def testLinkChange( self ):
|
||||
"Test TCLink bw and delay"
|
||||
p = pexpect.spawn( 'mn --link tc,bw=10,delay=10ms' )
|
||||
# test bw
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'iperf' )
|
||||
p.expect( r"Results: \['([\d\.]+) Mbits/sec'," )
|
||||
bw = float( p.match.group( 1 ) )
|
||||
self.assertTrue( bw < 10.1, 'Bandwidth %.2f >= 10.1 Mb/s' % bw )
|
||||
self.assertTrue( bw > 9.0, 'Bandwidth %.2f <= 9 Mb/s' % bw )
|
||||
p.expect( self.prompt )
|
||||
# test delay
|
||||
p.sendline( 'h1 ping -c 4 h2' )
|
||||
p.expect( r'rtt min/avg/max/mdev = '
|
||||
r'([\d\.]+)/([\d\.]+)/([\d\.]+)/([\d\.]+) ms' )
|
||||
delay = float( p.match.group( 2 ) )
|
||||
self.assertTrue( delay >= 40, 'Delay < 40ms' )
|
||||
self.assertTrue( delay <= 50, 'Delay > 50ms' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
def testVerbosity( self ):
|
||||
"Test debug and output verbosity"
|
||||
# test output
|
||||
p = pexpect.spawn( 'mn -v output' )
|
||||
p.expect( self.prompt )
|
||||
self.assertEqual( len( p.before ), 0, 'Too much output for "output"' )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
# test debug
|
||||
p = pexpect.spawn( 'mn -v debug --test none' )
|
||||
p.expect( pexpect.EOF )
|
||||
lines = p.before.split( '\n' )
|
||||
self.assertTrue( len( lines ) > 70, "Debug output is too short" )
|
||||
|
||||
def testCustomTopo( self ):
|
||||
"Start Mininet using a custom topo, then run pingall"
|
||||
# Satisfy pylint
|
||||
assert self
|
||||
custom = os.path.dirname( os.path.realpath( __file__ ) )
|
||||
custom = os.path.join( custom, '../../custom/topo-2sw-2host.py' )
|
||||
custom = os.path.normpath( custom )
|
||||
p = pexpect.spawn(
|
||||
'mn --custom %s --topo mytopo --test pingall' % custom )
|
||||
p.expect( '0% dropped' )
|
||||
p.expect( pexpect.EOF )
|
||||
|
||||
def testStaticMAC( self ):
|
||||
"Verify that MACs are set to easy to read numbers"
|
||||
p = pexpect.spawn( 'mn --mac' )
|
||||
p.expect( self.prompt )
|
||||
for i in range( 1, 3 ):
|
||||
p.sendline( 'h%d ifconfig' % i )
|
||||
p.expect( r'\s00:00:00:00:00:0%d\s' % i )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.expect( pexpect.EOF )
|
||||
|
||||
def testSwitches( self ):
|
||||
"Run iperf test using user and ovsk switches"
|
||||
switches = [ 'user', 'ovsk' ]
|
||||
for sw in switches:
|
||||
p = pexpect.spawn( 'mn --switch %s --test iperf' % sw )
|
||||
p.expect( r"Results: \['([\d\.]+) .bits/sec'," )
|
||||
bw = float( p.match.group( 1 ) )
|
||||
self.assertTrue( bw > 0 )
|
||||
p.expect( pexpect.EOF )
|
||||
|
||||
def testBenchmark( self ):
|
||||
"Run benchmark and verify that it takes less than 2 seconds"
|
||||
p = pexpect.spawn( 'mn --test none' )
|
||||
p.expect( r'completed in ([\d\.]+) seconds' )
|
||||
time = float( p.match.group( 1 ) )
|
||||
self.assertTrue( time < 2, 'Benchmark takes more than 2 seconds' )
|
||||
|
||||
def testOwnNamespace( self ):
|
||||
"Test running user switch in its own namespace"
|
||||
p = pexpect.spawn( 'mn --innamespace --switch user' )
|
||||
p.expect( self.prompt )
|
||||
interfaces = [ r'h1-eth0[:\s]', r's1-eth1[:\s]',
|
||||
r'[^-](eth|en)\w*\d[:\s]', r'lo[:\s]',
|
||||
self.prompt ]
|
||||
p.sendline( 's1 ifconfig -a' )
|
||||
ifcount = 0
|
||||
while True:
|
||||
index = p.expect( interfaces )
|
||||
if index == 1 or index == 3:
|
||||
ifcount += 1
|
||||
elif index == 0:
|
||||
self.fail( 'h1 interface displayed in "s1 ifconfig"' )
|
||||
elif index == 2:
|
||||
self.fail( 'eth0 displayed in "s1 ifconfig"' )
|
||||
else:
|
||||
break
|
||||
self.assertEqual( ifcount, 2, 'Missing interfaces on s1' )
|
||||
# verify that all hosts a reachable
|
||||
p.sendline( 'pingall' )
|
||||
p.expect( r'(\d+)% dropped' )
|
||||
dropped = int( p.match.group( 1 ) )
|
||||
self.assertEqual( dropped, 0, 'pingall failed')
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
# PART 3
|
||||
def testPythonInterpreter( self ):
|
||||
"Test py and px by checking IP for h1 and adding h3"
|
||||
p = pexpect.spawn( 'mn' )
|
||||
p.expect( self.prompt )
|
||||
# test host IP
|
||||
p.sendline( 'py h1.IP()' )
|
||||
p.expect( '10.0.0.1' )
|
||||
p.expect( self.prompt )
|
||||
# test adding host
|
||||
p.sendline( "px net.addHost('h3')" )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( "px net.addLink(s1, h3)" )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'net' )
|
||||
p.expect( 'h3' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'py h3.MAC()' )
|
||||
p.expect( '([a-f0-9]{2}:?){6}' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
def testLink( self ):
|
||||
"Test link CLI command using ping"
|
||||
p = pexpect.spawn( 'mn' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'link s1 h1 down' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 ping -c 1 h2' )
|
||||
p.expect( 'unreachable' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'link s1 h1 up' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'h1 ping -c 1 h2' )
|
||||
p.expect( '0% packet loss' )
|
||||
p.expect( self.prompt )
|
||||
p.sendline( 'exit' )
|
||||
p.wait()
|
||||
|
||||
@unittest.skipUnless( os.path.exists( '/tmp/pox' ) or
|
||||
'1 received' in quietRun( 'ping -c 1 github.com' ),
|
||||
'Github is not reachable; cannot download Pox' )
|
||||
def testRemoteController( self ):
|
||||
"Test Mininet using Pox controller"
|
||||
# Satisfy pylint
|
||||
assert self
|
||||
if not os.path.exists( '/tmp/pox' ):
|
||||
p = pexpect.spawn(
|
||||
'git clone https://github.com/noxrepo/pox.git /tmp/pox' )
|
||||
p.expect( pexpect.EOF )
|
||||
pox = pexpect.spawn( '/tmp/pox/pox.py forwarding.l2_learning' )
|
||||
net = pexpect.spawn(
|
||||
'mn --controller=remote,ip=127.0.0.1,port=6633 --test pingall' )
|
||||
net.expect( '0% dropped' )
|
||||
net.expect( pexpect.EOF )
|
||||
pox.sendintr()
|
||||
pox.wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
+265
-141
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
'''@package topo
|
||||
"""@package topo
|
||||
|
||||
Network topology creation.
|
||||
|
||||
@@ -9,222 +9,346 @@ This package includes code to represent network topologies.
|
||||
|
||||
A Topo object can be a topology database for NOX, can represent a physical
|
||||
setup for testing, and can even be emulated with the Mininet package.
|
||||
'''
|
||||
"""
|
||||
|
||||
# BL: we may have to fix compatibility here.
|
||||
# networkx is also a fairly heavyweight dependency
|
||||
# from networkx.classes.graph import Graph
|
||||
|
||||
from networkx import Graph
|
||||
from mininet.util import irange, natural, naturalSeq
|
||||
|
||||
class Topo(object):
|
||||
class MultiGraph( object ):
|
||||
"Utility class to track nodes and edges - replaces networkx.MultiGraph"
|
||||
|
||||
def __init__( self ):
|
||||
self.node = {}
|
||||
self.edge = {}
|
||||
|
||||
def add_node( self, node, attr_dict=None, **attrs):
|
||||
"""Add node to graph
|
||||
attr_dict: attribute dict (optional)
|
||||
attrs: more attributes (optional)
|
||||
warning: updates attr_dict with attrs"""
|
||||
attr_dict = {} if attr_dict is None else attr_dict
|
||||
attr_dict.update( attrs )
|
||||
self.node[ node ] = attr_dict
|
||||
|
||||
def add_edge( self, src, dst, key=None, attr_dict=None, **attrs ):
|
||||
"""Add edge to graph
|
||||
key: optional key
|
||||
attr_dict: optional attribute dict
|
||||
attrs: more attributes
|
||||
warning: udpates attr_dict with attrs"""
|
||||
attr_dict = {} if attr_dict is None else attr_dict
|
||||
attr_dict.update( attrs )
|
||||
self.node.setdefault( src, {} )
|
||||
self.node.setdefault( dst, {} )
|
||||
self.edge.setdefault( src, {} )
|
||||
self.edge.setdefault( dst, {} )
|
||||
self.edge[ src ].setdefault( dst, {} )
|
||||
entry = self.edge[ dst ][ src ] = self.edge[ src ][ dst ]
|
||||
# If no key, pick next ordinal number
|
||||
if key is None:
|
||||
keys = [ k for k in entry.keys() if isinstance( k, int ) ]
|
||||
key = max( [ 0 ] + keys ) + 1
|
||||
entry[ key ] = attr_dict
|
||||
return key
|
||||
|
||||
def nodes( self, data=False):
|
||||
"""Return list of graph nodes
|
||||
data: return list of ( node, attrs)"""
|
||||
return self.node.items() if data else self.node.keys()
|
||||
|
||||
def edges_iter( self, data=False, keys=False ):
|
||||
"Iterator: return graph edges, optionally with data and keys"
|
||||
for src, entry in self.edge.items():
|
||||
for dst, entrykeys in entry.items():
|
||||
if src > dst:
|
||||
# Skip duplicate edges
|
||||
continue
|
||||
for k, attrs in entrykeys.items():
|
||||
if data:
|
||||
if keys:
|
||||
yield( src, dst, k, attrs )
|
||||
else:
|
||||
yield( src, dst, attrs )
|
||||
else:
|
||||
if keys:
|
||||
yield( src, dst, k )
|
||||
else:
|
||||
yield( src, dst )
|
||||
|
||||
def edges( self, data=False, keys=False ):
|
||||
"Return list of graph edges"
|
||||
return list( self.edges_iter( data=data, keys=keys ) )
|
||||
|
||||
def __getitem__( self, node ):
|
||||
"Return link dict for given src node"
|
||||
return self.edge[ node ]
|
||||
|
||||
def __len__( self ):
|
||||
"Return the number of nodes"
|
||||
return len( self.node )
|
||||
|
||||
def convertTo( self, cls, data=False, keys=False ):
|
||||
"""Convert to a new object of networkx.MultiGraph-like class cls
|
||||
data: include node and edge data
|
||||
keys: include edge keys as well as edge data"""
|
||||
g = cls()
|
||||
g.add_nodes_from( self.nodes( data=data ) )
|
||||
g.add_edges_from( self.edges( data=( data or keys ), keys=keys ) )
|
||||
return g
|
||||
|
||||
|
||||
class Topo( object ):
|
||||
"Data center network representation for structured multi-trees."
|
||||
|
||||
def __init__(self, hopts=None, sopts=None, lopts=None):
|
||||
"""Topo object:
|
||||
def __init__( self, *args, **params ):
|
||||
"""Topo object.
|
||||
Optional named parameters:
|
||||
hinfo: default host options
|
||||
sopts: default switch options
|
||||
lopts: default link options"""
|
||||
self.g = Graph()
|
||||
self.node_info = {}
|
||||
self.link_info = {} # (src, dst) tuples hash to EdgeInfo objects
|
||||
self.hopts = {} if hopts is None else hopts
|
||||
self.sopts = {} if sopts is None else sopts
|
||||
self.lopts = {} if lopts is None else lopts
|
||||
self.ports = {} # ports[src][dst] is port on src that connects to dst
|
||||
lopts: default link options
|
||||
calls build()"""
|
||||
self.g = MultiGraph()
|
||||
self.hopts = params.pop( 'hopts', {} )
|
||||
self.sopts = params.pop( 'sopts', {} )
|
||||
self.lopts = params.pop( 'lopts', {} )
|
||||
# ports[src][dst][sport] is port on dst that connects to src
|
||||
self.ports = {}
|
||||
self.build( *args, **params )
|
||||
|
||||
def addNode(self, name, **opts):
|
||||
def build( self, *args, **params ):
|
||||
"Override this method to build your topology."
|
||||
pass
|
||||
|
||||
def addNode( self, name, **opts ):
|
||||
"""Add Node to graph.
|
||||
name: name
|
||||
opts: node options
|
||||
returns: node name"""
|
||||
self.g.add_node(name)
|
||||
self.node_info[name] = opts
|
||||
self.g.add_node( name, **opts )
|
||||
return name
|
||||
|
||||
def addHost(self, name, **opts):
|
||||
def addHost( self, name, **opts ):
|
||||
"""Convenience method: Add host to graph.
|
||||
name: host name
|
||||
opts: host options
|
||||
returns: host name"""
|
||||
if not opts and self.hopts:
|
||||
opts = self.hopts
|
||||
return self.addNode(name, **opts)
|
||||
return self.addNode( name, **opts )
|
||||
|
||||
def addSwitch(self, name, **opts):
|
||||
def addSwitch( self, name, **opts ):
|
||||
"""Convenience method: Add switch to graph.
|
||||
name: switch name
|
||||
opts: switch options
|
||||
returns: switch name"""
|
||||
if not opts and self.sopts:
|
||||
opts = self.sopts
|
||||
result = self.addNode(name, isSwitch=True, **opts)
|
||||
result = self.addNode( name, isSwitch=True, **opts )
|
||||
return result
|
||||
|
||||
def addLink(self, node1, node2, port1=None, port2=None,
|
||||
**opts):
|
||||
def addLink( self, node1, node2, port1=None, port2=None,
|
||||
key=None, **opts ):
|
||||
"""node1, node2: nodes to link together
|
||||
port1, port2: ports (optional)
|
||||
opts: link options (optional)
|
||||
returns: link info key"""
|
||||
if not opts and self.lopts:
|
||||
opts = self.lopts
|
||||
self.addPort(node1, node2, port1, port2)
|
||||
key = tuple(self.sorted([node1, node2]))
|
||||
self.link_info[key] = opts
|
||||
self.g.add_edge(*key)
|
||||
port1, port2 = self.addPort( node1, node2, port1, port2 )
|
||||
opts = dict( opts )
|
||||
opts.update( node1=node1, node2=node2, port1=port1, port2=port2 )
|
||||
self.g.add_edge(node1, node2, key, opts )
|
||||
return key
|
||||
|
||||
def addPort(self, src, dst, sport=None, dport=None):
|
||||
'''Generate port mapping for new edge.
|
||||
@param src source switch name
|
||||
@param dst destination switch name
|
||||
'''
|
||||
self.ports.setdefault(src, {})
|
||||
self.ports.setdefault(dst, {})
|
||||
# New port: number of outlinks + base
|
||||
src_base = 1 if self.isSwitch(src) else 0
|
||||
dst_base = 1 if self.isSwitch(dst) else 0
|
||||
if sport is None:
|
||||
sport = len(self.ports[src]) + src_base
|
||||
if dport is None:
|
||||
dport = len(self.ports[dst]) + dst_base
|
||||
self.ports[src][dst] = sport
|
||||
self.ports[dst][src] = dport
|
||||
|
||||
def nodes(self, sort=True):
|
||||
def nodes( self, sort=True ):
|
||||
"Return nodes in graph"
|
||||
if sort:
|
||||
return self.sorted( self.g.nodes() )
|
||||
else:
|
||||
return self.g.nodes()
|
||||
|
||||
def isSwitch(self, n):
|
||||
'''Returns true if node is a switch.'''
|
||||
info = self.node_info[n]
|
||||
return info and info.get('isSwitch', False)
|
||||
def isSwitch( self, n ):
|
||||
"Returns true if node is a switch."
|
||||
return self.g.node[ n ].get( 'isSwitch', False )
|
||||
|
||||
def switches(self, sort=True):
|
||||
'''Return switches.
|
||||
sort: sort switches alphabetically
|
||||
@return dpids list of dpids
|
||||
'''
|
||||
return [n for n in self.nodes(sort) if self.isSwitch(n)]
|
||||
def switches( self, sort=True ):
|
||||
"""Return switches.
|
||||
sort: sort switches alphabetically
|
||||
returns: dpids list of dpids"""
|
||||
return [ n for n in self.nodes( sort ) if self.isSwitch( n ) ]
|
||||
|
||||
def hosts(self, sort=True):
|
||||
'''Return hosts.
|
||||
sort: sort hosts alphabetically
|
||||
@return dpids list of dpids
|
||||
'''
|
||||
return [n for n in self.nodes(sort) if not self.isSwitch(n)]
|
||||
def hosts( self, sort=True ):
|
||||
"""Return hosts.
|
||||
sort: sort hosts alphabetically
|
||||
returns: list of hosts"""
|
||||
return [ n for n in self.nodes( sort ) if not self.isSwitch( n ) ]
|
||||
|
||||
def links(self, sort=True):
|
||||
'''Return links.
|
||||
sort: sort links alphabetically
|
||||
@return links list of name pairs
|
||||
'''
|
||||
def iterLinks( self, withKeys=False, withInfo=False ):
|
||||
"""Return links (iterator)
|
||||
withKeys: return link keys
|
||||
withInfo: return link info
|
||||
returns: list of ( src, dst [,key, info ] )"""
|
||||
for _src, _dst, key, info in self.g.edges_iter( data=True, keys=True ):
|
||||
node1, node2 = info[ 'node1' ], info[ 'node2' ]
|
||||
if withKeys:
|
||||
if withInfo:
|
||||
yield( node1, node2, key, info )
|
||||
else:
|
||||
yield( node1, node2, key )
|
||||
else:
|
||||
if withInfo:
|
||||
yield( node1, node2, info )
|
||||
else:
|
||||
yield( node1, node2 )
|
||||
|
||||
def links( self, sort=False, withKeys=False, withInfo=False ):
|
||||
"""Return links
|
||||
sort: sort links alphabetically, preserving (src, dst) order
|
||||
withKeys: return link keys
|
||||
withInfo: return link info
|
||||
returns: list of ( src, dst [,key, info ] )"""
|
||||
links = list( self.iterLinks( withKeys, withInfo ) )
|
||||
if not sort:
|
||||
return self.g.edges()
|
||||
else:
|
||||
links = [tuple(self.sorted(e)) for e in self.g.edges()]
|
||||
return sorted( links, key=naturalSeq )
|
||||
return links
|
||||
# Ignore info when sorting
|
||||
tupleSize = 3 if withKeys else 2
|
||||
return sorted( links, key=( lambda l: naturalSeq( l[ :tupleSize ] ) ) )
|
||||
|
||||
def port(self, src, dst):
|
||||
'''Get port number.
|
||||
# This legacy port management mechanism is clunky and will probably
|
||||
# be removed at some point.
|
||||
|
||||
@param src source switch name
|
||||
@param dst destination switch name
|
||||
@return tuple (src_port, dst_port):
|
||||
src_port: port on source switch leading to the destination switch
|
||||
dst_port: port on destination switch leading to the source switch
|
||||
'''
|
||||
if src in self.ports and dst in self.ports[src]:
|
||||
assert dst in self.ports and src in self.ports[dst]
|
||||
return (self.ports[src][dst], self.ports[dst][src])
|
||||
def addPort( self, src, dst, sport=None, dport=None ):
|
||||
"""Generate port mapping for new edge.
|
||||
src: source switch name
|
||||
dst: destination switch name"""
|
||||
# Initialize if necessary
|
||||
ports = self.ports
|
||||
ports.setdefault( src, {} )
|
||||
ports.setdefault( dst, {} )
|
||||
# New port: number of outlinks + base
|
||||
if sport is None:
|
||||
src_base = 1 if self.isSwitch( src ) else 0
|
||||
sport = len( ports[ src ] ) + src_base
|
||||
if dport is None:
|
||||
dst_base = 1 if self.isSwitch( dst ) else 0
|
||||
dport = len( ports[ dst ] ) + dst_base
|
||||
ports[ src ][ sport ] = ( dst, dport )
|
||||
ports[ dst ][ dport ] = ( src, sport )
|
||||
return sport, dport
|
||||
|
||||
def linkInfo( self, src, dst ):
|
||||
"Return link metadata"
|
||||
src, dst = self.sorted([src, dst])
|
||||
return self.link_info[(src, dst)]
|
||||
def port( self, src, dst ):
|
||||
"""Get port numbers.
|
||||
src: source switch name
|
||||
dst: destination switch name
|
||||
sport: optional source port (otherwise use lowest src port)
|
||||
returns: tuple (sport, dport), where
|
||||
sport = port on source switch leading to the destination switch
|
||||
dport = port on destination switch leading to the source switch
|
||||
Note that you can also look up ports using linkInfo()"""
|
||||
# A bit ugly and slow vs. single-link implementation ;-(
|
||||
ports = [ ( sport, entry[ 1 ] )
|
||||
for sport, entry in self.ports[ src ].items()
|
||||
if entry[ 0 ] == dst ]
|
||||
return ports if len( ports ) != 1 else ports[ 0 ]
|
||||
|
||||
def setlinkInfo( self, src, dst, info ):
|
||||
"Set link metadata"
|
||||
src, dst = self.sorted([src, dst])
|
||||
self.link_info[(src, dst)] = info
|
||||
def _linkEntry( self, src, dst, key=None ):
|
||||
"Helper function: return link entry and key"
|
||||
entry = self.g[ src ][ dst ]
|
||||
if key is None:
|
||||
key = min( entry )
|
||||
return entry, key
|
||||
|
||||
def linkInfo( self, src, dst, key=None ):
|
||||
"Return link metadata dict"
|
||||
entry, key = self._linkEntry( src, dst, key )
|
||||
return entry[ key ]
|
||||
|
||||
def setlinkInfo( self, src, dst, info, key=None ):
|
||||
"Set link metadata dict"
|
||||
entry, key = self._linkEntry( src, dst, key )
|
||||
entry[ key ] = info
|
||||
|
||||
def nodeInfo( self, name ):
|
||||
"Return metadata (dict) for node"
|
||||
info = self.node_info[ name ]
|
||||
return info if info is not None else {}
|
||||
return self.g.node[ name ]
|
||||
|
||||
def setNodeInfo( self, name, info ):
|
||||
"Set metadata (dict) for node"
|
||||
self.node_info[ name ] = info
|
||||
self.g.node[ name ] = info
|
||||
|
||||
def convertTo( self, cls, data=True, keys=True ):
|
||||
"""Convert to a new object of networkx.MultiGraph-like class cls
|
||||
data: include node and edge data (default True)
|
||||
keys: include edge keys as well as edge data (default True)"""
|
||||
return self.g.convertTo( cls, data=data, keys=keys )
|
||||
|
||||
@staticmethod
|
||||
def sorted( items ):
|
||||
"Items sorted in natural (i.e. alphabetical) order"
|
||||
return sorted(items, key=natural)
|
||||
return sorted( items, key=natural )
|
||||
|
||||
class SingleSwitchTopo(Topo):
|
||||
'''Single switch connected to k hosts.'''
|
||||
|
||||
def __init__(self, k=2, **opts):
|
||||
'''Init.
|
||||
# Our idiom defines additional parameters in build(param...)
|
||||
# pylint: disable=arguments-differ
|
||||
|
||||
@param k number of hosts
|
||||
@param enable_all enables all nodes and switches?
|
||||
'''
|
||||
super(SingleSwitchTopo, self).__init__(**opts)
|
||||
class SingleSwitchTopo( Topo ):
|
||||
"Single switch connected to k hosts."
|
||||
|
||||
def build( self, k=2, **_opts ):
|
||||
"k: number of hosts"
|
||||
self.k = k
|
||||
|
||||
switch = self.addSwitch('s1')
|
||||
for h in irange(1, k):
|
||||
host = self.addHost('h%s' % h)
|
||||
self.addLink(host, switch)
|
||||
switch = self.addSwitch( 's1' )
|
||||
for h in irange( 1, k ):
|
||||
host = self.addHost( 'h%s' % h )
|
||||
self.addLink( host, switch )
|
||||
|
||||
|
||||
class SingleSwitchReversedTopo(Topo):
|
||||
'''Single switch connected to k hosts, with reversed ports.
|
||||
class SingleSwitchReversedTopo( Topo ):
|
||||
"""Single switch connected to k hosts, with reversed ports.
|
||||
The lowest-numbered host is connected to the highest-numbered port.
|
||||
Useful to verify that Mininet properly handles custom port
|
||||
numberings."""
|
||||
|
||||
The lowest-numbered host is connected to the highest-numbered port.
|
||||
|
||||
Useful to verify that Mininet properly handles custom port numberings.
|
||||
'''
|
||||
def __init__(self, k=2, **opts):
|
||||
'''Init.
|
||||
|
||||
@param k number of hosts
|
||||
@param enable_all enables all nodes and switches?
|
||||
'''
|
||||
super(SingleSwitchReversedTopo, self).__init__(**opts)
|
||||
def build( self, k=2 ):
|
||||
"k: number of hosts"
|
||||
self.k = k
|
||||
switch = self.addSwitch('s1')
|
||||
for h in irange(1, k):
|
||||
host = self.addHost('h%s' % h)
|
||||
self.addLink(host, switch,
|
||||
port1=0, port2=(k - h + 1))
|
||||
switch = self.addSwitch( 's1' )
|
||||
for h in irange( 1, k ):
|
||||
host = self.addHost( 'h%s' % h )
|
||||
self.addLink( host, switch,
|
||||
port1=0, port2=( k - h + 1 ) )
|
||||
|
||||
class LinearTopo(Topo):
|
||||
"Linear topology of k switches, with one host per switch."
|
||||
|
||||
def __init__(self, k=2, **opts):
|
||||
"""Init.
|
||||
k: number of switches (and hosts)
|
||||
hconf: host configuration options
|
||||
lconf: link configuration options"""
|
||||
class MinimalTopo( SingleSwitchTopo ):
|
||||
"Minimal topology with two hosts and one switch"
|
||||
def build( self ):
|
||||
return SingleSwitchTopo.build( self, k=2 )
|
||||
|
||||
super(LinearTopo, self).__init__(**opts)
|
||||
|
||||
class LinearTopo( Topo ):
|
||||
"Linear topology of k switches, with n hosts per switch."
|
||||
|
||||
def build( self, k=2, n=1, **_opts):
|
||||
"""k: number of switches
|
||||
n: number of hosts per switch"""
|
||||
self.k = k
|
||||
self.n = n
|
||||
|
||||
if n == 1:
|
||||
genHostName = lambda i, j: 'h%s' % i
|
||||
else:
|
||||
genHostName = lambda i, j: 'h%ss%d' % ( j, i )
|
||||
|
||||
lastSwitch = None
|
||||
for i in irange(1, k):
|
||||
host = self.addHost('h%s' % i)
|
||||
switch = self.addSwitch('s%s' % i)
|
||||
self.addLink( host, switch)
|
||||
for i in irange( 1, k ):
|
||||
# Add switch
|
||||
switch = self.addSwitch( 's%s' % i )
|
||||
# Add hosts to switch
|
||||
for j in irange( 1, n ):
|
||||
host = self.addHost( genHostName( i, j ) )
|
||||
self.addLink( host, switch )
|
||||
# Connect switch to previous
|
||||
if lastSwitch:
|
||||
self.addLink( switch, lastSwitch)
|
||||
self.addLink( switch, lastSwitch )
|
||||
lastSwitch = switch
|
||||
|
||||
# pylint: enable=arguments-differ
|
||||
|
||||
+48
-2
@@ -3,11 +3,13 @@
|
||||
from mininet.topo import Topo
|
||||
from mininet.net import Mininet
|
||||
|
||||
# The build() method is expected to do this:
|
||||
# pylint: disable=arguments-differ
|
||||
|
||||
class TreeTopo( Topo ):
|
||||
"Topology for a tree network with a given depth and fanout."
|
||||
|
||||
def __init__( self, depth=1, fanout=2 ):
|
||||
super( TreeTopo, self ).__init__()
|
||||
def build( self, depth=1, fanout=2 ):
|
||||
# Numbering: h1..N, s1..M
|
||||
self.hostNum = 1
|
||||
self.switchNum = 1
|
||||
@@ -34,3 +36,47 @@ def TreeNet( depth=1, fanout=2, **kwargs ):
|
||||
"Convenience function for creating tree networks."
|
||||
topo = TreeTopo( depth, fanout )
|
||||
return Mininet( topo, **kwargs )
|
||||
|
||||
|
||||
class TorusTopo( Topo ):
|
||||
"""2-D Torus topology
|
||||
WARNING: this topology has LOOPS and WILL NOT WORK
|
||||
with the default controller or any Ethernet bridge
|
||||
without STP turned on! It can be used with STP, e.g.:
|
||||
# mn --topo torus,3,3 --switch lxbr,stp=1 --test pingall"""
|
||||
|
||||
def build( self, x, y, n=1 ):
|
||||
"""x: dimension of torus in x-direction
|
||||
y: dimension of torus in y-direction
|
||||
n: number of hosts per switch"""
|
||||
if x < 3 or y < 3:
|
||||
raise Exception( 'Please use 3x3 or greater for compatibility '
|
||||
'with 2.1' )
|
||||
if n == 1:
|
||||
genHostName = lambda loc, k: 'h%s' % ( loc )
|
||||
else:
|
||||
genHostName = lambda loc, k: 'h%sx%d' % ( loc, k )
|
||||
|
||||
hosts, switches, dpid = {}, {}, 0
|
||||
# Create and wire interior
|
||||
for i in range( 0, x ):
|
||||
for j in range( 0, y ):
|
||||
loc = '%dx%d' % ( i + 1, j + 1 )
|
||||
# dpid cannot be zero for OVS
|
||||
dpid = ( i + 1 ) * 256 + ( j + 1 )
|
||||
switch = switches[ i, j ] = self.addSwitch(
|
||||
's' + loc, dpid='%x' % dpid )
|
||||
for k in range( 0, n ):
|
||||
host = hosts[ i, j, k ] = self.addHost(
|
||||
genHostName( loc, k + 1 ) )
|
||||
self.addLink( host, switch )
|
||||
# Connect switches
|
||||
for i in range( 0, x ):
|
||||
for j in range( 0, y ):
|
||||
sw1 = switches[ i, j ]
|
||||
sw2 = switches[ i, ( j + 1 ) % y ]
|
||||
sw3 = switches[ ( i + 1 ) % x, j ]
|
||||
self.addLink( sw1, sw2 )
|
||||
self.addLink( sw1, sw3 )
|
||||
|
||||
# pylint: enable=arguments-differ
|
||||
|
||||
+279
-93
@@ -1,15 +1,50 @@
|
||||
"Utility functions for Mininet."
|
||||
|
||||
from mininet.log import output, info, error, warn
|
||||
|
||||
from mininet.log import output, info, error, warn, debug
|
||||
|
||||
from time import sleep
|
||||
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
|
||||
from resource import getrlimit, setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
|
||||
from select import poll, POLLIN, POLLHUP
|
||||
from subprocess import call, check_call, Popen, PIPE, STDOUT
|
||||
import re
|
||||
from fcntl import fcntl, F_GETFL, F_SETFL
|
||||
from os import O_NONBLOCK
|
||||
import os
|
||||
from functools import partial
|
||||
import sys
|
||||
|
||||
# Python 2/3 compatibility
|
||||
Python3 = sys.version_info[0] == 3
|
||||
BaseString = str if Python3 else getattr( str, '__base__' )
|
||||
Encoding = 'utf-8' if Python3 else None
|
||||
def decode( s ):
|
||||
"Decode a byte string if needed for Python 3"
|
||||
return s.decode( Encoding ) if Python3 else s
|
||||
def encode( s ):
|
||||
"Encode a byte string if needed for Python 3"
|
||||
return s.encode( Encoding ) if Python3 else s
|
||||
try:
|
||||
# pylint: disable=import-error
|
||||
oldpexpect = None
|
||||
import pexpect as oldpexpect
|
||||
# pylint: enable=import-error
|
||||
|
||||
class Pexpect( object ):
|
||||
"Custom pexpect that is compatible with str"
|
||||
@staticmethod
|
||||
def spawn( *args, **kwargs):
|
||||
"pexpect.spawn that is compatible with str"
|
||||
if Python3 and 'encoding' not in kwargs:
|
||||
kwargs.update( encoding='utf-8' )
|
||||
return oldpexpect.spawn( *args, **kwargs )
|
||||
|
||||
def __getattr__( self, name ):
|
||||
return getattr( oldpexpect, name )
|
||||
pexpect = Pexpect()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
# Command execution support
|
||||
|
||||
@@ -24,7 +59,7 @@ def checkRun( cmd ):
|
||||
return check_call( cmd.split( ' ' ) )
|
||||
|
||||
# pylint doesn't understand explicit type checking
|
||||
# pylint: disable-msg=E1103
|
||||
# pylint: disable=maybe-no-member
|
||||
|
||||
def oldQuietRun( *cmd ):
|
||||
"""Run a command, routing stderr to stdout, and return the output.
|
||||
@@ -55,18 +90,13 @@ def oldQuietRun( *cmd ):
|
||||
# This is a bit complicated, but it enables us to
|
||||
# monitor command output as it is happening
|
||||
|
||||
# pylint: disable=too-many-branches,too-many-statements
|
||||
def errRun( *cmd, **kwargs ):
|
||||
"""Run a command and return stdout, stderr and return code
|
||||
cmd: string or list of command and args
|
||||
stderr: STDOUT to merge stderr with stdout
|
||||
shell: run command using shell
|
||||
echo: monitor output to console"""
|
||||
# Allow passing in a list or a string
|
||||
if len( cmd ) == 1:
|
||||
cmd = cmd[ 0 ]
|
||||
if isinstance( cmd, str ):
|
||||
cmd = cmd.split( ' ' )
|
||||
cmd = [ str( arg ) for arg in cmd ]
|
||||
# By default we separate stderr, don't run in a shell, and don't echo
|
||||
stderr = kwargs.get( 'stderr', PIPE )
|
||||
shell = kwargs.get( 'shell', False )
|
||||
@@ -74,6 +104,15 @@ def errRun( *cmd, **kwargs ):
|
||||
if echo:
|
||||
# cmd goes to stderr, output goes to stdout
|
||||
info( cmd, '\n' )
|
||||
if len( cmd ) == 1:
|
||||
cmd = cmd[ 0 ]
|
||||
# Allow passing in a list or a string
|
||||
if isinstance( cmd, str ) and not shell:
|
||||
cmd = cmd.split( ' ' )
|
||||
cmd = [ str( arg ) for arg in cmd ]
|
||||
elif isinstance( cmd, list ) and shell:
|
||||
cmd = " ".join( arg for arg in cmd )
|
||||
debug( '*** errRun:', cmd, '\n' )
|
||||
popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell )
|
||||
# We use poll() because select() doesn't work with large fd numbers,
|
||||
# and thus communicate() doesn't work either
|
||||
@@ -88,21 +127,37 @@ def errRun( *cmd, **kwargs ):
|
||||
errDone = False
|
||||
while not outDone or not errDone:
|
||||
readable = poller.poll()
|
||||
for fd, _event in readable:
|
||||
for fd, event in readable:
|
||||
f = fdtofile[ fd ]
|
||||
data = f.read( 1024 )
|
||||
if echo:
|
||||
output( data )
|
||||
if f == popen.stdout:
|
||||
out += data
|
||||
if data == '':
|
||||
if event & POLLIN:
|
||||
data = f.read( 1024 )
|
||||
if Python3:
|
||||
data = data.decode( Encoding )
|
||||
if echo:
|
||||
output( data )
|
||||
if f == popen.stdout:
|
||||
out += data
|
||||
if data == '':
|
||||
outDone = True
|
||||
elif f == popen.stderr:
|
||||
err += data
|
||||
if data == '':
|
||||
errDone = True
|
||||
else: # POLLHUP or something unexpected
|
||||
if f == popen.stdout:
|
||||
outDone = True
|
||||
elif f == popen.stderr:
|
||||
err += data
|
||||
if data == '':
|
||||
elif f == popen.stderr:
|
||||
errDone = True
|
||||
poller.unregister( fd )
|
||||
|
||||
returncode = popen.wait()
|
||||
# Python 3 complains if we don't explicitly close these
|
||||
popen.stdout.close()
|
||||
if stderr == PIPE:
|
||||
popen.stderr.close()
|
||||
debug( out, err, returncode )
|
||||
return out, err, returncode
|
||||
# pylint: enable=too-many-branches
|
||||
|
||||
def errFail( *cmd, **kwargs ):
|
||||
"Run a command using errRun and raise exception on nonzero exit"
|
||||
@@ -116,8 +171,7 @@ def quietRun( cmd, **kwargs ):
|
||||
"Run a command and return merged stdout and stderr"
|
||||
return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ]
|
||||
|
||||
# pylint: enable-msg=E1103
|
||||
# pylint: disable-msg=E1101
|
||||
# pylint: enable=maybe-no-member
|
||||
|
||||
def isShellBuiltin( cmd ):
|
||||
"Return True if cmd is a bash builtin."
|
||||
@@ -130,8 +184,6 @@ def isShellBuiltin( cmd ):
|
||||
|
||||
isShellBuiltin.builtIns = None
|
||||
|
||||
# pylint: enable-msg=E1101
|
||||
|
||||
# Interface management
|
||||
#
|
||||
# Interfaces are managed as strings which are simply the
|
||||
@@ -145,17 +197,41 @@ isShellBuiltin.builtIns = None
|
||||
# live in the root namespace and thus do not have to be
|
||||
# explicitly moved.
|
||||
|
||||
def makeIntfPair( intf1, intf2 ):
|
||||
"""Make a veth pair connecting intf1 and intf2.
|
||||
intf1: string, interface
|
||||
intf2: string, interface
|
||||
returns: success boolean"""
|
||||
# Delete any old interfaces with the same names
|
||||
quietRun( 'ip link del ' + intf1 )
|
||||
quietRun( 'ip link del ' + intf2 )
|
||||
def makeIntfPair( intf1, intf2, addr1=None, addr2=None, node1=None, node2=None,
|
||||
deleteIntfs=True, runCmd=None ):
|
||||
"""Make a veth pair connnecting new interfaces intf1 and intf2
|
||||
intf1: name for interface 1
|
||||
intf2: name for interface 2
|
||||
addr1: MAC address for interface 1 (optional)
|
||||
addr2: MAC address for interface 2 (optional)
|
||||
node1: home node for interface 1 (optional)
|
||||
node2: home node for interface 2 (optional)
|
||||
deleteIntfs: delete intfs before creating them
|
||||
runCmd: function to run shell commands (quietRun)
|
||||
raises Exception on failure"""
|
||||
if not runCmd:
|
||||
runCmd = quietRun if not node1 else node1.cmd
|
||||
runCmd2 = quietRun if not node2 else node2.cmd
|
||||
if deleteIntfs:
|
||||
# Delete any old interfaces with the same names
|
||||
runCmd( 'ip link del ' + intf1 )
|
||||
runCmd2( 'ip link del ' + intf2 )
|
||||
# Create new pair
|
||||
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
|
||||
return quietRun( cmd )
|
||||
netns = 1 if not node2 else node2.pid
|
||||
if addr1 is None and addr2 is None:
|
||||
cmdOutput = runCmd( 'ip link add name %s '
|
||||
'type veth peer name %s '
|
||||
'netns %s' % ( intf1, intf2, netns ) )
|
||||
else:
|
||||
cmdOutput = runCmd( 'ip link add name %s '
|
||||
'address %s '
|
||||
'type veth peer name %s '
|
||||
'address %s '
|
||||
'netns %s' %
|
||||
( intf1, addr1, intf2, addr2, netns ) )
|
||||
if cmdOutput:
|
||||
raise Exception( "Error creating interface pair (%s,%s): %s " %
|
||||
( intf1, intf2, cmdOutput ) )
|
||||
|
||||
def retry( retries, delaySecs, fn, *args, **keywords ):
|
||||
"""Try something several times before giving up.
|
||||
@@ -171,35 +247,32 @@ def retry( retries, delaySecs, fn, *args, **keywords ):
|
||||
error( "*** gave up after %i retries\n" % tries )
|
||||
exit( 1 )
|
||||
|
||||
def moveIntfNoRetry( intf, dstNode, srcNode=None, printError=False ):
|
||||
def moveIntfNoRetry( intf, dstNode, printError=False ):
|
||||
"""Move interface to node, without retrying.
|
||||
intf: string, interface
|
||||
dstNode: destination Node
|
||||
srcNode: source Node or None (default) for root ns
|
||||
printError: if true, print error"""
|
||||
intf = str( intf )
|
||||
cmd = 'ip link set %s netns %s' % ( intf, dstNode.pid )
|
||||
if srcNode:
|
||||
srcNode.cmd( cmd )
|
||||
else:
|
||||
quietRun( cmd )
|
||||
links = dstNode.cmd( 'ip link show' )
|
||||
if not ( ' %s:' % intf ) in links:
|
||||
cmdOutput = quietRun( cmd )
|
||||
# If ip link set does not produce any output, then we can assume
|
||||
# that the link has been moved successfully.
|
||||
if cmdOutput:
|
||||
if printError:
|
||||
error( '*** Error: moveIntf: ' + intf +
|
||||
' not successfully moved to ' + dstNode.name + '\n' )
|
||||
' not successfully moved to ' + dstNode.name + ':\n',
|
||||
cmdOutput )
|
||||
return False
|
||||
return True
|
||||
|
||||
def moveIntf( intf, dstNode, srcNode=None, printError=False,
|
||||
retries=3, delaySecs=0.001 ):
|
||||
def moveIntf( intf, dstNode, printError=True,
|
||||
retries=3, delaySecs=0.001 ):
|
||||
"""Move interface to node, retrying on failure.
|
||||
intf: string, interface
|
||||
dstNode: destination Node
|
||||
srcNode: source Node or None (default) for root ns
|
||||
printError: if true, print error"""
|
||||
retry( retries, delaySecs, moveIntfNoRetry, intf, dstNode,
|
||||
srcNode=srcNode, printError=printError )
|
||||
printError=printError )
|
||||
|
||||
# Support for dumping network
|
||||
|
||||
@@ -227,6 +300,15 @@ def dumpNetConnections( net ):
|
||||
nodes = net.controllers + net.switches + net.hosts
|
||||
dumpNodeConnections( nodes )
|
||||
|
||||
def dumpPorts( switches ):
|
||||
"dump interface to openflow port mappings for each switch"
|
||||
for switch in switches:
|
||||
output( '%s ' % switch.name )
|
||||
for intf in switch.intfList():
|
||||
port = switch.ports[ intf ]
|
||||
output( '%s:%d ' % ( intf, port ) )
|
||||
output( '\n' )
|
||||
|
||||
# IP and Mac address formatting and parsing
|
||||
|
||||
def _colonHex( val, bytecount ):
|
||||
@@ -269,7 +351,7 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
|
||||
ipBaseNum: option base IP address as int
|
||||
returns IP address as string"""
|
||||
imax = 0xffffffff >> prefixLen
|
||||
assert i <= imax
|
||||
assert i <= imax, 'Not enough IP addresses in the subnet'
|
||||
mask = 0xffffffff ^ imax
|
||||
ipnum = ( ipBaseNum & mask ) + i
|
||||
return ipStr( ipnum )
|
||||
@@ -277,6 +359,8 @@ def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ):
|
||||
def ipParse( ip ):
|
||||
"Parse an IP address and return an unsigned int."
|
||||
args = [ int( arg ) for arg in ip.split( '.' ) ]
|
||||
while len(args) < 4:
|
||||
args.insert( len(args) - 1, 0 )
|
||||
return ipNum( *args )
|
||||
|
||||
def netParse( ipstr ):
|
||||
@@ -286,6 +370,10 @@ def netParse( ipstr ):
|
||||
if '/' in ipstr:
|
||||
ip, pf = ipstr.split( '/' )
|
||||
prefixLen = int( pf )
|
||||
#if no prefix is specified, set the prefix to 24
|
||||
else:
|
||||
ip = ipstr
|
||||
prefixLen = 24
|
||||
return ipParse( ip ), prefixLen
|
||||
|
||||
def checkInt( s ):
|
||||
@@ -325,51 +413,99 @@ def pmonitor(popens, timeoutms=500, readline=True,
|
||||
terminates: when all EOFs received"""
|
||||
poller = poll()
|
||||
fdToHost = {}
|
||||
for host, popen in popens.iteritems():
|
||||
for host, popen in popens.items():
|
||||
fd = popen.stdout.fileno()
|
||||
fdToHost[ fd ] = host
|
||||
poller.register( fd, POLLIN )
|
||||
if not readline:
|
||||
# Use non-blocking reads
|
||||
flags = fcntl( fd, F_GETFL )
|
||||
fcntl( fd, F_SETFL, flags | O_NONBLOCK )
|
||||
poller.register( fd, POLLIN | POLLHUP )
|
||||
flags = fcntl( fd, F_GETFL )
|
||||
fcntl( fd, F_SETFL, flags | O_NONBLOCK )
|
||||
while popens:
|
||||
fds = poller.poll( timeoutms )
|
||||
if fds:
|
||||
for fd, event in fds:
|
||||
host = fdToHost[ fd ]
|
||||
popen = popens[ host ]
|
||||
if event & POLLIN:
|
||||
if readline:
|
||||
# Attempt to read a line of output
|
||||
# This blocks until we receive a newline!
|
||||
line = popen.stdout.readline()
|
||||
else:
|
||||
line = popen.stdout.read( readmax )
|
||||
yield host, line
|
||||
# Check for EOF
|
||||
elif event & POLLHUP:
|
||||
if event & POLLIN or event & POLLHUP:
|
||||
while True:
|
||||
try:
|
||||
f = popen.stdout
|
||||
line = decode( f.readline() if readline
|
||||
else f.read( readmax ) )
|
||||
except IOError:
|
||||
line = ''
|
||||
if line == '':
|
||||
break
|
||||
yield host, line
|
||||
if event & POLLHUP:
|
||||
poller.unregister( fd )
|
||||
del popens[ host ]
|
||||
else:
|
||||
yield None, ''
|
||||
|
||||
# Other stuff we use
|
||||
def sysctlTestAndSet( name, limit ):
|
||||
"Helper function to set sysctl limits"
|
||||
#convert non-directory names into directory names
|
||||
if '/' not in name:
|
||||
name = '/proc/sys/' + name.replace( '.', '/' )
|
||||
#read limit
|
||||
with open( name, 'r' ) as readFile:
|
||||
oldLimit = readFile.readline()
|
||||
if isinstance( limit, int ):
|
||||
#compare integer limits before overriding
|
||||
if int( oldLimit ) < limit:
|
||||
with open( name, 'w' ) as writeFile:
|
||||
writeFile.write( "%d" % limit )
|
||||
else:
|
||||
#overwrite non-integer limits
|
||||
with open( name, 'w' ) as writeFile:
|
||||
writeFile.write( limit )
|
||||
|
||||
def rlimitTestAndSet( name, limit ):
|
||||
"Helper function to set rlimits"
|
||||
soft, hard = getrlimit( name )
|
||||
if soft < limit:
|
||||
hardLimit = hard if limit < hard else limit
|
||||
setrlimit( name, ( limit, hardLimit ) )
|
||||
|
||||
def fixLimits():
|
||||
"Fix ridiculously small resource limits."
|
||||
setrlimit( RLIMIT_NPROC, ( 8192, 8192 ) )
|
||||
setrlimit( RLIMIT_NOFILE, ( 16384, 16384 ) )
|
||||
debug( "*** Setting resource limits\n" )
|
||||
try:
|
||||
rlimitTestAndSet( RLIMIT_NPROC, 8192 )
|
||||
rlimitTestAndSet( RLIMIT_NOFILE, 16384 )
|
||||
#Increase open file limit
|
||||
sysctlTestAndSet( 'fs.file-max', 10000 )
|
||||
#Increase network buffer space
|
||||
sysctlTestAndSet( 'net.core.wmem_max', 16777216 )
|
||||
sysctlTestAndSet( 'net.core.rmem_max', 16777216 )
|
||||
sysctlTestAndSet( 'net.ipv4.tcp_rmem', '10240 87380 16777216' )
|
||||
sysctlTestAndSet( 'net.ipv4.tcp_wmem', '10240 87380 16777216' )
|
||||
sysctlTestAndSet( 'net.core.netdev_max_backlog', 5000 )
|
||||
#Increase arp cache size
|
||||
sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh1', 4096 )
|
||||
sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh2', 8192 )
|
||||
sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh3', 16384 )
|
||||
#Increase routing table size
|
||||
sysctlTestAndSet( 'net.ipv4.route.max_size', 32768 )
|
||||
#Increase number of PTYs for nodes
|
||||
sysctlTestAndSet( 'kernel.pty.max', 20000 )
|
||||
# pylint: disable=broad-except
|
||||
except Exception:
|
||||
warn( "*** Error setting resource limits. "
|
||||
"Mininet's performance may be affected.\n" )
|
||||
# pylint: enable=broad-except
|
||||
|
||||
|
||||
def mountCgroups():
|
||||
"Make sure cgroups file system is mounted"
|
||||
mounts = quietRun( 'mount' )
|
||||
mounts = quietRun( 'grep cgroup /proc/mounts' )
|
||||
cgdir = '/sys/fs/cgroup'
|
||||
csdir = cgdir + '/cpuset'
|
||||
if ('cgroup on %s' % cgdir not in mounts and
|
||||
'cgroups on %s' % cgdir not in mounts):
|
||||
if ('cgroup %s' % cgdir not in mounts and
|
||||
'cgroups %s' % cgdir not in mounts):
|
||||
raise Exception( "cgroups not mounted on " + cgdir )
|
||||
if 'cpuset on %s' % csdir not in mounts:
|
||||
if 'cpuset %s' % csdir not in mounts:
|
||||
errRun( 'mkdir -p ' + csdir )
|
||||
errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir )
|
||||
|
||||
@@ -378,7 +514,7 @@ def natural( text ):
|
||||
def num( s ):
|
||||
"Convert text segment to int if necessary"
|
||||
return int( s ) if s.isdigit() else s
|
||||
return [ num( s ) for s in re.split( r'(\d+)', text ) ]
|
||||
return [ num( s ) for s in re.split( r'(\d+)', str( text ) ) ]
|
||||
|
||||
def naturalSeq( t ):
|
||||
"Natural sort key function for sequences"
|
||||
@@ -423,35 +559,58 @@ def splitArgs( argstr ):
|
||||
args = [ makeNumeric( s ) for s in params if '=' not in s ]
|
||||
kwargs = {}
|
||||
for s in [ p for p in params if '=' in p ]:
|
||||
key, val = s.split( '=' )
|
||||
key, val = s.split( '=', 1 )
|
||||
kwargs[ key ] = makeNumeric( val )
|
||||
return fn, args, kwargs
|
||||
|
||||
def customConstructor( constructors, argStr ):
|
||||
"""Return custom constructor based on argStr
|
||||
The args and key/val pairs in argsStr will be automatically applied
|
||||
when the generated constructor is later used.
|
||||
def customClass( classes, argStr ):
|
||||
"""Return customized class based on argStr
|
||||
The args and key/val pairs in argStr will be automatically applied
|
||||
when the generated class is later used.
|
||||
"""
|
||||
cname, newargs, kwargs = splitArgs( argStr )
|
||||
constructor = constructors.get( cname, None )
|
||||
|
||||
if not constructor:
|
||||
cname, args, kwargs = splitArgs( argStr )
|
||||
cls = classes.get( cname, None )
|
||||
if not cls:
|
||||
raise Exception( "error: %s is unknown - please specify one of %s" %
|
||||
( cname, constructors.keys() ) )
|
||||
( cname, classes.keys() ) )
|
||||
if not args and not kwargs:
|
||||
return cls
|
||||
|
||||
def customized( name, *args, **params ):
|
||||
"Customized constructor, useful for Node, Link, and other classes"
|
||||
params = params.copy()
|
||||
params.update( kwargs )
|
||||
if not newargs:
|
||||
return constructor( name, *args, **params )
|
||||
if args:
|
||||
warn( 'warning: %s replacing %s with %s\n' % (
|
||||
constructor, args, newargs ) )
|
||||
return constructor( name, *newargs, **params )
|
||||
return specialClass( cls, append=args, defaults=kwargs )
|
||||
|
||||
def specialClass( cls, prepend=None, append=None,
|
||||
defaults=None, override=None ):
|
||||
"""Like functools.partial, but it returns a class
|
||||
prepend: arguments to prepend to argument list
|
||||
append: arguments to append to argument list
|
||||
defaults: default values for keyword arguments
|
||||
override: keyword arguments to override"""
|
||||
|
||||
if prepend is None:
|
||||
prepend = []
|
||||
|
||||
if append is None:
|
||||
append = []
|
||||
|
||||
if defaults is None:
|
||||
defaults = {}
|
||||
|
||||
if override is None:
|
||||
override = {}
|
||||
|
||||
class CustomClass( cls ):
|
||||
"Customized subclass with preset args/params"
|
||||
def __init__( self, *args, **params ):
|
||||
newparams = defaults.copy()
|
||||
newparams.update( params )
|
||||
newparams.update( override )
|
||||
cls.__init__( self, *( list( prepend ) + list( args ) +
|
||||
list( append ) ),
|
||||
**newparams )
|
||||
|
||||
CustomClass.__name__ = '%s%s' % ( cls.__name__, defaults )
|
||||
return CustomClass
|
||||
|
||||
customized.__name__ = 'customConstructor(%s)' % argStr
|
||||
return customized
|
||||
|
||||
def buildTopo( topos, topoStr ):
|
||||
"""Create topology from string with format (object, arg1, arg2,...).
|
||||
@@ -468,6 +627,33 @@ def ensureRoot():
|
||||
Probably we should only sudo when needed as per Big Switch's patch.
|
||||
"""
|
||||
if os.getuid() != 0:
|
||||
print "*** Mininet must run as root."
|
||||
error( '*** Mininet must run as root.\n' )
|
||||
exit( 1 )
|
||||
return
|
||||
|
||||
def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ):
|
||||
"""Wait until server is listening on port.
|
||||
returns True if server is listening"""
|
||||
runCmd = ( client.cmd if client else
|
||||
partial( quietRun, shell=True ) )
|
||||
if not runCmd( 'which telnet' ):
|
||||
raise Exception('Could not find telnet' )
|
||||
# pylint: disable=maybe-no-member
|
||||
serverIP = server if isinstance( server, BaseString ) else server.IP()
|
||||
cmd = ( 'echo A | telnet -e A %s %s' % ( serverIP, port ) )
|
||||
time = 0
|
||||
result = runCmd( cmd )
|
||||
while 'Connected' not in result:
|
||||
if 'No route' in result:
|
||||
rtable = runCmd( 'route' )
|
||||
error( 'no route to %s:\n%s' % ( server, rtable ) )
|
||||
return False
|
||||
if timeout and time >= timeout:
|
||||
error( 'could not connect to %s on port %d\n' % ( server, port ) )
|
||||
return False
|
||||
debug( 'waiting for', server, 'to listen on port', port, '\n' )
|
||||
info( '.' )
|
||||
sleep( .5 )
|
||||
time += .5
|
||||
result = runCmd( cmd )
|
||||
return True
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*
|
||||
* - closing all file descriptors except stdin/out/error
|
||||
* - detaching from a controlling tty using setsid
|
||||
* - running in a network namespace
|
||||
* - running in network and mount namespaces
|
||||
* - printing out the pid of a process so we can identify it later
|
||||
* - attaching to a namespace and cgroup
|
||||
* - setting RT scheduling
|
||||
@@ -13,6 +13,7 @@
|
||||
* Partially based on public domain setsid(1)
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <linux/sched.h>
|
||||
#include <unistd.h>
|
||||
@@ -20,23 +21,24 @@
|
||||
#include <syscall.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
#include <sched.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/mount.h>
|
||||
|
||||
#if !defined(VERSION)
|
||||
#define VERSION "(devel)"
|
||||
#endif
|
||||
|
||||
void usage(char *name)
|
||||
void usage(char *name)
|
||||
{
|
||||
printf("Execution utility for Mininet\n\n"
|
||||
"Usage: %s [-cdnp] [-a pid] [-g group] [-r rtprio] cmd args...\n\n"
|
||||
"Options:\n"
|
||||
" -c: close all file descriptors except stdin/out/error\n"
|
||||
" -d: detach from tty by calling setsid()\n"
|
||||
" -n: run in new network namespace\n"
|
||||
" -n: run in new network and mount namespaces\n"
|
||||
" -p: print ^A + pid\n"
|
||||
" -a pid: attach to pid's network namespace\n"
|
||||
" -a pid: attach to pid's network and mount namespaces\n"
|
||||
" -g group: add to cgroup\n"
|
||||
" -r rtprio: run with SCHED_RR (usually requires -g)\n"
|
||||
" -v: print version\n",
|
||||
@@ -46,7 +48,7 @@ void usage(char *name)
|
||||
|
||||
int setns(int fd, int nstype)
|
||||
{
|
||||
return syscall(__NR_setns, fd, nstype);
|
||||
return syscall(__NR_setns, fd, nstype);
|
||||
}
|
||||
|
||||
/* Validate alphanumeric path foo1/bar2/baz */
|
||||
@@ -62,7 +64,7 @@ void validate(char *path)
|
||||
}
|
||||
|
||||
/* Add our pid to cgroup */
|
||||
int cgroup(char *gname)
|
||||
void cgroup(char *gname)
|
||||
{
|
||||
static char path[PATH_MAX];
|
||||
static char *groups[] = {
|
||||
@@ -92,11 +94,13 @@ int cgroup(char *gname)
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char c;
|
||||
int c;
|
||||
int fd;
|
||||
char path[PATH_MAX];
|
||||
int nsid;
|
||||
int pid;
|
||||
char *cwd = get_current_dir_name();
|
||||
|
||||
static struct sched_param sp;
|
||||
while ((c = getopt(argc, argv, "+cdnpa:g:r:vh")) != -1)
|
||||
switch(c) {
|
||||
@@ -112,20 +116,35 @@ int main(int argc, char *argv[])
|
||||
case -1:
|
||||
perror("fork");
|
||||
return 1;
|
||||
case 0: /* child */
|
||||
case 0: /* child */
|
||||
break;
|
||||
default: /* parent */
|
||||
default: /* parent */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
setsid();
|
||||
break;
|
||||
case 'n':
|
||||
/* run in network namespace */
|
||||
if (unshare(CLONE_NEWNET) == -1) {
|
||||
/* run in network and mount namespaces */
|
||||
if (unshare(CLONE_NEWNET|CLONE_NEWNS) == -1) {
|
||||
perror("unshare");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Mark our whole hierarchy recursively as private, so that our
|
||||
* mounts do not propagate to other processes.
|
||||
*/
|
||||
|
||||
if (mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) == -1) {
|
||||
perror("remount");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* mount sysfs to pick up the new network namespace */
|
||||
if (mount("sysfs", "/sys", "sysfs", MS_MGC_VAL, NULL) == -1) {
|
||||
perror("mount");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
/* print pid */
|
||||
@@ -133,9 +152,9 @@ int main(int argc, char *argv[])
|
||||
fflush(stdout);
|
||||
break;
|
||||
case 'a':
|
||||
/* Attach to pid's network namespace */
|
||||
/* Attach to pid's network namespace and mount namespace */
|
||||
pid = atoi(optarg);
|
||||
sprintf(path, "/proc/%d/ns/net", pid );
|
||||
sprintf(path, "/proc/%d/ns/net", pid);
|
||||
nsid = open(path, O_RDONLY);
|
||||
if (nsid < 0) {
|
||||
perror(path);
|
||||
@@ -145,6 +164,22 @@ int main(int argc, char *argv[])
|
||||
perror("setns");
|
||||
return 1;
|
||||
}
|
||||
/* Plan A: call setns() to attach to mount namespace */
|
||||
sprintf(path, "/proc/%d/ns/mnt", pid);
|
||||
nsid = open(path, O_RDONLY);
|
||||
if (nsid < 0 || setns(nsid, 0) != 0) {
|
||||
/* Plan B: chroot/chdir into pid's root file system */
|
||||
sprintf(path, "/proc/%d/root", pid);
|
||||
if (chroot(path) < 0) {
|
||||
perror(path);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
/* chdir to correct working directory */
|
||||
if (chdir(cwd) != 0) {
|
||||
perror(cwd);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
/* Attach to cgroup */
|
||||
@@ -166,7 +201,7 @@ int main(int argc, char *argv[])
|
||||
exit(0);
|
||||
default:
|
||||
usage(argv[0]);
|
||||
exit(1);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (optind < argc) {
|
||||
@@ -174,6 +209,8 @@ int main(int argc, char *argv[])
|
||||
perror(argv[optind]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
usage(argv[0]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -20,25 +20,24 @@ setup(
|
||||
description='Process-based OpenFlow emulator',
|
||||
author='Bob Lantz',
|
||||
author_email='rlantz@cs.stanford.edu',
|
||||
packages=find_packages(exclude='test'),
|
||||
packages=[ 'mininet', 'mininet.examples' ],
|
||||
long_description="""
|
||||
Mininet is a network emulator which uses lightweight
|
||||
virtualization to create virtual networks for rapid
|
||||
prototyping of Software-Defined Network (SDN) designs
|
||||
using OpenFlow. http://openflow.org/mininet
|
||||
using OpenFlow. http://mininet.org
|
||||
""",
|
||||
classifiers=[
|
||||
"License :: OSI Approved :: BSD License",
|
||||
"Programming Language :: Python",
|
||||
"Development Status :: 2 - Pre-Alpha",
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Internet",
|
||||
"Topic :: System :: Emulators",
|
||||
],
|
||||
keywords='networking emulator protocol Internet OpenFlow SDN',
|
||||
license='BSD',
|
||||
install_requires=[
|
||||
'setuptools',
|
||||
'networkx'
|
||||
'setuptools'
|
||||
],
|
||||
scripts=scripts,
|
||||
)
|
||||
|
||||
Executable
+182
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Mininet ssh authentication script for cluster edition
|
||||
# This script will create a single key pair, which is then
|
||||
# propagated throughout the entire cluster.
|
||||
# There are two options for setup; temporary setup
|
||||
# persistent setup. If no options are specified, and the script
|
||||
# is only given ip addresses or host names, it will default to
|
||||
# the temporary setup. An ssh directory is then created in
|
||||
# /tmp/mn/ssh on each node, and mounted with the keys over the
|
||||
# user's ssh directory. This setup can easily be torn down by running
|
||||
# clustersetup with the -c option.
|
||||
# If the -p option is used, the setup will be persistent. In this
|
||||
# case, the key pair will be be distributed directly to each node's
|
||||
# ssh directory, but will be called cluster_key. An option to
|
||||
# specify this key for use will be added to the config file in each
|
||||
# user's ssh directory.
|
||||
|
||||
|
||||
set -e
|
||||
num_options=0
|
||||
persistent=false
|
||||
showHelp=false
|
||||
clean=false
|
||||
declare -a hosts=()
|
||||
user=$(whoami)
|
||||
SSHDIR=/tmp/mn/ssh
|
||||
USERDIR=$HOME/.ssh
|
||||
usage="./clustersetup.sh [ -p|h|c ] [ host1 ] [ host2 ] ...\n
|
||||
Authenticate yourself and other cluster nodes to each other
|
||||
via ssh for mininet cluster edition. By default, we use a
|
||||
temporary ssh setup. An ssh directory is mounted over
|
||||
$USERDIR on each machine in the cluster.
|
||||
|
||||
-h: display this help
|
||||
-p: create a persistent ssh setup. This will add
|
||||
new ssh keys and known_hosts to each nodes
|
||||
$USERDIR directory
|
||||
-c: method to clean up a temporary ssh setup.
|
||||
Any hosts taken as arguments will be cleaned
|
||||
"
|
||||
|
||||
persistentSetup() {
|
||||
echo "***creating key pair"
|
||||
ssh-keygen -t rsa -C "Cluster_Edition_Key" -f $USERDIR/cluster_key -N '' &> /dev/null
|
||||
cat $USERDIR/cluster_key.pub >> $USERDIR/authorized_keys
|
||||
echo "***configuring ssh"
|
||||
echo "IdentityFile $USERDIR/cluster_key" >> $USERDIR/config
|
||||
echo "IdentityFile $USERDIR/id_rsa" >> $USERDIR/config
|
||||
|
||||
for host in $hosts; do
|
||||
echo "***copying public key to $host"
|
||||
ssh-copy-id -i $USERDIR/cluster_key.pub $user@$host &> /dev/null
|
||||
echo "***copying key pair to remote host"
|
||||
scp $USERDIR/cluster_key $user@$host:$USERDIR
|
||||
scp $USERDIR/cluster_key.pub $user@$host:$USERDIR
|
||||
echo "***configuring remote host"
|
||||
ssh -o ForwardAgent=yes $user@$host "
|
||||
echo 'IdentityFile $USERDIR/cluster_key' >> $USERDIR/config
|
||||
echo 'IdentityFile $USERDIR/id_rsa' >> $USERDIR/config"
|
||||
done
|
||||
|
||||
for host in $hosts; do
|
||||
echo "***copying known_hosts to $host"
|
||||
scp $USERDIR/known_hosts $user@$host:$USERDIR/cluster_known_hosts
|
||||
ssh $user@$host "
|
||||
cat $USERDIR/cluster_known_hosts >> $USERDIR/known_hosts
|
||||
rm $USERDIR/cluster_known_hosts"
|
||||
done
|
||||
}
|
||||
|
||||
tempSetup() {
|
||||
|
||||
echo "***creating temporary ssh directory"
|
||||
mkdir -p $SSHDIR
|
||||
echo "***creating key pair"
|
||||
ssh-keygen -t rsa -C "Cluster_Edition_Key" -f $SSHDIR/id_rsa -N '' &> /dev/null
|
||||
|
||||
echo "***mounting temporary ssh directory"
|
||||
sudo mount --bind $SSHDIR $USERDIR
|
||||
cp $SSHDIR/id_rsa.pub $SSHDIR/authorized_keys
|
||||
|
||||
for host in $hosts; do
|
||||
echo "***copying public key to $host"
|
||||
ssh-copy-id $user@$host &> /dev/null
|
||||
echo "***mounting remote temporary ssh directory for $host"
|
||||
ssh -o ForwardAgent=yes $user@$host "
|
||||
mkdir -p $SSHDIR
|
||||
cp $USERDIR/authorized_keys $SSHDIR/authorized_keys
|
||||
sudo mount --bind $SSHDIR $USERDIR"
|
||||
echo "***copying key pair to $host"
|
||||
scp $SSHDIR/{id_rsa,id_rsa.pub} $user@$host:$SSHDIR
|
||||
done
|
||||
|
||||
for host in $hosts; do
|
||||
echo "***copying known_hosts to $host"
|
||||
scp $SSHDIR/known_hosts $user@$host:$SSHDIR
|
||||
done
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
|
||||
for host in $hosts; do
|
||||
echo "***cleaning up $host"
|
||||
ssh $user@$host "sudo umount $USERDIR
|
||||
sudo rm -rf $SSHDIR"
|
||||
done
|
||||
|
||||
echo "**unmounting local directories"
|
||||
sudo umount $USERDIR
|
||||
echo "***removing temporary ssh directory"
|
||||
sudo rm -rf $SSHDIR
|
||||
echo "done!"
|
||||
|
||||
}
|
||||
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "ERROR: No Arguments"
|
||||
echo "$usage"
|
||||
exit
|
||||
else
|
||||
while getopts 'hpc' OPTION
|
||||
do
|
||||
((num_options+=1))
|
||||
case $OPTION in
|
||||
h) showHelp=true;;
|
||||
p) persistent=true;;
|
||||
c) clean=true;;
|
||||
?) showHelp=true;;
|
||||
esac
|
||||
done
|
||||
shift $(($OPTIND - 1))
|
||||
fi
|
||||
|
||||
if [ "$num_options" -gt 1 ]; then
|
||||
echo "ERROR: Too Many Options"
|
||||
echo "$usage"
|
||||
exit
|
||||
fi
|
||||
|
||||
if $showHelp; then
|
||||
echo "$usage"
|
||||
exit
|
||||
fi
|
||||
|
||||
for i in "$@"; do
|
||||
output=$(getent ahostsv4 "$i")
|
||||
if [ -z "$output" ]; then
|
||||
echo '***WARNING: could not find hostname "$i"'
|
||||
echo ""
|
||||
else
|
||||
hosts+="$i "
|
||||
fi
|
||||
done
|
||||
|
||||
if $clean; then
|
||||
cleanup
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "***authenticating to:"
|
||||
for host in $hosts; do
|
||||
echo "$host"
|
||||
done
|
||||
|
||||
echo
|
||||
|
||||
if $persistent; then
|
||||
echo '***Setting up persistent SSH configuration between all nodes'
|
||||
persistentSetup
|
||||
echo $'\n*** Sucessfully set up ssh throughout the cluster!'
|
||||
|
||||
else
|
||||
echo '*** Setting up temporary SSH configuration between all nodes'
|
||||
tempSetup
|
||||
echo $'\n***Finished temporary setup. When you are done with your cluster'
|
||||
echo $' session, tear down the SSH connections with'
|
||||
echo $' ./clustersetup.sh -c '$hosts''
|
||||
fi
|
||||
|
||||
echo
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user