From d5e94cab9f93fcc33a50a88f6abc1990db2b7a10 Mon Sep 17 00:00:00 2001 From: David Fialho Date: Wed, 30 Aug 2017 19:26:31 +0100 Subject: [PATCH 01/19] Bump version to 0.2-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ccbe582..e9068f6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ group 'ssbgp' -version '1.0-SNAPSHOT' +version '2.0-SNAPSHOT' buildscript { ext.kotlin_version = '1.1.4-2' From 50541fe86fb0a99fc84c9edac1cd2bd819c9cabb Mon Sep 17 00:00:00 2001 From: David Fialho Date: Wed, 30 Aug 2017 19:27:57 +0100 Subject: [PATCH 02/19] Bump kotlin version to 1.1.4-3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e9068f6..57fba32 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ group 'ssbgp' version '2.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.1.4-2' + ext.kotlin_version = '1.1.4-3' ext.dokka_version = '0.9.15' ext.junit_version = '1.0.0-M4' ext.junit5_version = '5.0.0-M4' From 5801d7181b3dd65ef926cc0ad8dfabfce4e09dbe Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 31 Aug 2017 11:26:52 +0100 Subject: [PATCH 03/19] Add function parseInterdomainExtender() --- src/main/kotlin/io/ExtenderParseFunctions.kt | 43 ++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/main/kotlin/io/ExtenderParseFunctions.kt diff --git a/src/main/kotlin/io/ExtenderParseFunctions.kt b/src/main/kotlin/io/ExtenderParseFunctions.kt new file mode 100644 index 0000000..914f1a5 --- /dev/null +++ b/src/main/kotlin/io/ExtenderParseFunctions.kt @@ -0,0 +1,43 @@ +package io + +import bgp.BGPRoute +import bgp.policies.interdomain.* +import core.routing.Extender + +/** + * Created on 31-08-2017 + * + * @author David Fialho + * + * This file contains functions to parse extenders from labels. + */ + +/** + * Parses an Interdomain Extender. The supported labels are: + * + * R+ - parsed as a PeerplusExtender + * C - parsed as a CustomerExtender + * R - parsed as a PeerExtender + * P - parsed as a ProviderExtender + * S - parsed as a SiblingExtender + * + * This function is NOT case sensitive! + * + * @param label the label of the extender + * @param lineNumber the number of the line in which the label was found (used for the parse exception message only) + * @return the extender parsed from the label + * @throws ParseException if the label is not recognized + */ +@Throws(ParseException::class) +fun parseInterdomainExtender(label: String, lineNumber: Int = 0): Extender { + + return when (label.toLowerCase()) { + "r+" -> PeerplusExtender + "c" -> CustomerExtender + "r" -> PeerExtender + "p" -> ProviderExtender + "s" -> SiblingExtender + else -> throw ParseException("Extender label `$label` was not recognized: " + + "must be either R+, C, R, P, or S", lineNumber) + } +} \ No newline at end of file From 962e68192725cacfa98bf7f582f85e0f2093e1f7 Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 31 Aug 2017 11:27:04 +0100 Subject: [PATCH 04/19] Add helper extension funciton toNonNegativeInt() --- src/main/kotlin/utils/HelperFunctions.kt | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/main/kotlin/utils/HelperFunctions.kt diff --git a/src/main/kotlin/utils/HelperFunctions.kt b/src/main/kotlin/utils/HelperFunctions.kt new file mode 100644 index 0000000..ce5b6e6 --- /dev/null +++ b/src/main/kotlin/utils/HelperFunctions.kt @@ -0,0 +1,26 @@ +package utils + +/** + * Created on 31-08-2017 + * + * @author David Fialho + * + * This file contains a set of helper functions and extension function. + */ + +/** + * Parses a string as a non-negative integer number and returns the result + * + * @return the non-negative integer + * @throws NumberFormatException - if the string is not a valid representation of a number. + */ +@Throws(NumberFormatException::class) +fun String.toNonNegativeInt(): Int { + + val value = this.toInt() + if (value < 0) { + throw NumberFormatException() + } + + return value +} From d8fb93b74cec714f9f5fa73fd7d75ff53934f693 Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 31 Aug 2017 11:27:18 +0100 Subject: [PATCH 05/19] Refactor interdomain topology reader --- .../kotlin/io/InterdomainTopologyReader.kt | 40 ++++--------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/src/main/kotlin/io/InterdomainTopologyReader.kt b/src/main/kotlin/io/InterdomainTopologyReader.kt index abd19f8..fea8323 100644 --- a/src/main/kotlin/io/InterdomainTopologyReader.kt +++ b/src/main/kotlin/io/InterdomainTopologyReader.kt @@ -4,9 +4,9 @@ import bgp.BGP import bgp.BGPRoute import bgp.ISSBGP import bgp.SSBGP -import bgp.policies.interdomain.* import core.routing.* import io.TopologyParser.Handler +import utils.toNonNegativeInt import java.io.* /** @@ -54,7 +54,11 @@ class InterdomainTopologyReader(reader: Reader): TopologyReader, Closeable, Hand } val protocolLabel = values[0] - val mrai = parseNonNegativeInteger(values[1], currentLine) + val mrai = try { + values[1].toNonNegativeInt() + } catch (e: NumberFormatException) { + throw ParseException("Failed to parse `${values[1]}`: must be a non-negative integer number", currentLine) + } val protocol = when (protocolLabel.toLowerCase()) { "bgp" -> BGP(mrai) @@ -86,7 +90,7 @@ class InterdomainTopologyReader(reader: Reader): TopologyReader, Closeable, Hand throw ParseException("Link is missing required values: extender label", currentLine) } - val extender = parseExtender(values[0], currentLine) + val extender = parseInterdomainExtender(values[0], currentLine) try { builder.link(tail, head, extender) @@ -98,36 +102,6 @@ class InterdomainTopologyReader(reader: Reader): TopologyReader, Closeable, Hand } } - @Throws(ParseException::class) - private fun parseNonNegativeInteger(value: String, currentLine: Int): Int { - - try { - val intValue = value.toInt() - if (intValue < 0) { - throw NumberFormatException() - } - - return intValue - - } catch (e: NumberFormatException) { - throw ParseException("Failed to parse value `$value`: must be a non-negative integer value", currentLine) - } - } - - @Throws(ParseException::class) - private fun parseExtender(label: String, currentLine: Int): Extender { - - return when (label) { - "r+" -> PeerplusExtender - "c" -> CustomerExtender - "r" -> PeerExtender - "p" -> ProviderExtender - "s" -> SiblingExtender - else -> throw ParseException("Extender label `$label` was not recognized: " + - "must be either R+, C, R, P, or S", currentLine) - } - } - /** * Closes the stream and releases any system resources associated with it. */ From 16da64fb6cb2fd3c1b82522d54eaaaf1b2aa5b12 Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 31 Aug 2017 11:40:13 +0100 Subject: [PATCH 06/19] Add StubParser implementation --- src/main/kotlin/io/StubParser.kt | 115 ++++++++++++++++++++ src/test/kotlin/io/StubParserTest.kt | 152 +++++++++++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 src/main/kotlin/io/StubParser.kt create mode 100644 src/test/kotlin/io/StubParserTest.kt diff --git a/src/main/kotlin/io/StubParser.kt b/src/main/kotlin/io/StubParser.kt new file mode 100644 index 0000000..1e4939a --- /dev/null +++ b/src/main/kotlin/io/StubParser.kt @@ -0,0 +1,115 @@ +package io + +import core.routing.NodeID +import utils.toNonNegativeInt +import java.io.* + +/** + * Created on 31-08-2017 + * + * @author David Fialho + */ +class StubParser(reader: Reader): Closeable { + + companion object { + + /** + * Creates a stub parser with a file reader to parse a file. + * + * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for + * some other reason cannot be opened for reading. + */ + @Throws(FileNotFoundException::class) + fun useFile(stubFile: File): StubParser { + return StubParser(FileReader(stubFile)) + } + + } + + /** + * Interface for an handler that is called when a new stub item is parsed. + */ + interface Handler { + + /** + * Invoked when a new stub item is found. + * + * @param id the ID of the stub + * @param inNeighbor the ID of the stub's in-neighbor + * @param label the label of the extender associated with the link between the neighbor and the stub + * @param currentLine line number where the stub link was parsed + */ + fun onStubLink(id: NodeID, inNeighbor: NodeID, label: String, currentLine: Int) + + } + + private val reader = BufferedReader(reader) + + /** + * Parses the stub file and notifies the handler once a new stub is parsed. + * + * @param handler the handler that will be notified of new stub items + * @throws IOException If an I/O error occurs + * @throws ParseException If a parse error occurs + */ + @Throws(IOException::class, ParseException::class) + fun parse(handler: Handler) { + + var line: String? = reader.readLine() ?: return + var currentLine = 1 + + while (line != null) { + + // Ignore blank lines + if (!line.isBlank()) + parseLine(line, handler, currentLine) + + line = reader.readLine() + currentLine++ + } + + } + + private fun parseLine(line: String, handler: Handler, currentLine: Int) { + + val values = line.split("|").map { it.trim() } + + if (values.size != 3) { + throw ParseException("A stub item requires 3 values, but ${values.size} were provided", currentLine) + } + + val stubID = try { + values[0].toNonNegativeInt() + } catch (e: NumberFormatException) { + throw ParseException("Stub ID must be non-negative integer number: was `${values[0]}`", currentLine) + } + + val inNeighborID = try { + values[1].toNonNegativeInt() + } catch (e: NumberFormatException) { + throw ParseException("In-neighbor ID must be non-negative integer number: was `${values[1]}`", currentLine) + } + + val label = values[2] + if (label.isBlank()) throw ParseException("Stub item is missing label value", currentLine) + + handler.onStubLink(stubID, inNeighborID, label, currentLine) + } + + /** + * Resets the input stream. + * + * @throws IOException If the stream does not support reset(), or if some other I/O error occurs + */ + @Throws(IOException::class) + fun reset() { + reader.reset() + } + + /** + * Closes the input stream. + */ + override fun close() { + reader.close() + } +} \ No newline at end of file diff --git a/src/test/kotlin/io/StubParserTest.kt b/src/test/kotlin/io/StubParserTest.kt new file mode 100644 index 0000000..6bb9c78 --- /dev/null +++ b/src/test/kotlin/io/StubParserTest.kt @@ -0,0 +1,152 @@ +package io + +import com.nhaarman.mockito_kotlin.* +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.`is` as Is +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.given +import org.jetbrains.spek.api.dsl.on +import org.jetbrains.spek.api.dsl.it +import org.junit.jupiter.api.Assertions.assertThrows +import java.io.StringReader + +/** + * Created on 31-08-2017 + * + * @author David Fialho + */ +object StubParserTest: Spek({ + + given("an empty file") { + + val fileContent = "" + val handler: StubParser.Handler = mock() + + on("parsing the file") { + + it("does not throw anything") { + StubParser(StringReader(fileContent)).use { + it.parse(handler) + } + } + + it("does not parse any stub item") { + verify(handler, never()).onStubLink(any(), any(), any(), any()) + } + } + } + + given("file with single line `1 | 2 | C`") { + + val fileContent = "1 | 2 | C" + val handler: StubParser.Handler = mock() + + on("parsing the file") { + + StubParser(StringReader(fileContent)).use { + it.parse(handler) + } + + it("parsed a single stub") { + verify(handler, times(1)).onStubLink(any(), any(), any(), any()) + } + + it("parsed in line 1 a stub with ID 1, neighbor 2, and extender with label `C`") { + verify(handler, times(1)).onStubLink(id = 1, inNeighbor = 2, label = "C", currentLine = 1) + } + } + } + + fun lines(vararg lines: String): String = lines.joinToString("\n") + + given("file with single lines `1 | 2 | C`, `1 | 3 | R`") { + + val fileContent = lines( + "1 | 2 | C", + "1 | 3 | R" + ) + val handler: StubParser.Handler = mock() + + on("parsing the file") { + + StubParser(StringReader(fileContent)).use { + it.parse(handler) + } + + it("parsed two stubs") { + verify(handler, times(2)).onStubLink(any(), any(), any(), any()) + } + + it("parsed in line 1 a stub with ID 1, neighbor 2, and extender with label `C`") { + verify(handler, times(1)).onStubLink(id = 1, inNeighbor = 2, label = "C", currentLine = 1) + } + + it("parsed in line 2 a stub with ID 1, neighbor 3, and extender with label `R`") { + verify(handler, times(1)).onStubLink(id = 1, inNeighbor = 3, label = "R", currentLine = 2) + } + } + } + + given("file with single lines `1 | 2 | C`, ` `, `1 | 3 | R`") { + + val fileContent = lines( + "1 | 2 | C", + " ", + "1 | 3 | R" + ) + val handler: StubParser.Handler = mock() + + on("parsing the file") { + + StubParser(StringReader(fileContent)).use { + it.parse(handler) + } + + it("parsed two stubs") { + verify(handler, times(2)).onStubLink(any(), any(), any(), any()) + } + + it("parsed in line 1 a stub with ID 1, neighbor 2, and extender with label `C`") { + verify(handler, times(1)).onStubLink(id = 1, inNeighbor = 2, label = "C", currentLine = 1) + } + + it("parsed in line 3 a stub with ID 1, neighbor 3, and extender with label `R`") { + verify(handler, times(1)).onStubLink(id = 1, inNeighbor = 3, label = "R", currentLine = 3) + } + } + } + + val incorrectLines = listOf( + "1 | 2 ", + "1 | 2 |", + "a | 2 | C", + "1 | b | C", + " | | " + ) + + incorrectLines.forEach { line -> + + given("file with incorrect line `$line`") { + + val handler: StubParser.Handler = mock() + + on("parsing the file") { + + var exception: ParseException? = null + + it("throws a ParseException") { + StubParser(StringReader(line)).use { + exception = assertThrows(ParseException::class.java) { + it.parse(handler) + } + } + } + + it("indicates the error is in line 1") { + assertThat(exception?.lineNumber, Is(1)) + } + } + } + } + +}) \ No newline at end of file From ee439fb8a293dbd751ee623c275c7d7aef79b0d1 Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 31 Aug 2017 11:57:51 +0100 Subject: [PATCH 07/19] Refactor TopologyBuilder to follow Builder pattern --- src/main/kotlin/core/routing/TopologyBuilder.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/core/routing/TopologyBuilder.kt b/src/main/kotlin/core/routing/TopologyBuilder.kt index 676e8af..eb8d0d8 100644 --- a/src/main/kotlin/core/routing/TopologyBuilder.kt +++ b/src/main/kotlin/core/routing/TopologyBuilder.kt @@ -28,14 +28,17 @@ class TopologyBuilder { * * @param id the ID to identify the new node * @param protocol the protocol deployed by the new node + * @return this builder * @throws ElementExistsException if a node with the specified ID was already added to the builder */ @Throws(ElementExistsException::class) - fun addNode(id: NodeID, protocol: Protocol) { + fun addNode(id: NodeID, protocol: Protocol): TopologyBuilder { if (nodes.putIfAbsent(id, Node(id, protocol)) != null) { throw ElementExistsException("Node with ID `$id` was added twice to the topology builder") } + + return this } /** @@ -47,11 +50,12 @@ class TopologyBuilder { * @param from the Id of the node at the tail of the link * @param to the protocol deployed by the new node * @param extender the protocol deployed by the new node + * @return this builder * @throws ElementExistsException if a node with the specified ID was already added to the builder * @throws ElementNotFoundException if builder is missing the node with ID [from] and/or [to] */ @Throws(ElementExistsException::class, ElementNotFoundException::class) - fun link(from: NodeID, to: NodeID, extender: Extender) { + fun link(from: NodeID, to: NodeID, extender: Extender): TopologyBuilder { val tail = nodes[from] ?: throw ElementNotFoundException("Node with ID `$from` was not yet added the builder") val head = nodes[to] ?: throw ElementNotFoundException("Node with ID `$to` was not yet added the builder") @@ -61,6 +65,8 @@ class TopologyBuilder { } head.addInNeighbor(tail, extender) + + return this } /** From fbfadcf6640e829e6350454d4f7aa287f1c1aa9f Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 31 Aug 2017 12:24:28 +0100 Subject: [PATCH 08/19] Fix stub parser reset issues The reset() method is not implemented by all reader classes. For instance, StringReader does not implement it. Resetting such readers would cause a error indicating the the stream was not marked. This is solved by checking for mark support before reseting the reader. --- src/main/kotlin/io/StubParser.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/io/StubParser.kt b/src/main/kotlin/io/StubParser.kt index 1e4939a..a5a47cf 100644 --- a/src/main/kotlin/io/StubParser.kt +++ b/src/main/kotlin/io/StubParser.kt @@ -103,7 +103,8 @@ class StubParser(reader: Reader): Closeable { */ @Throws(IOException::class) fun reset() { - reader.reset() + if (!reader.markSupported()) + reader.reset() } /** From 7930398e1c5daa92d4e00a5bc416ec0400b12dc6 Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 31 Aug 2017 12:52:21 +0100 Subject: [PATCH 09/19] Add StubDB implementation --- src/main/kotlin/io/ExtenderParseFunctions.kt | 6 +- src/main/kotlin/simulation/StubDB.kt | 89 ++++++++++++++++++ src/test/kotlin/simulation/StubDBTest.kt | 96 ++++++++++++++++++++ 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/simulation/StubDB.kt create mode 100644 src/test/kotlin/simulation/StubDBTest.kt diff --git a/src/main/kotlin/io/ExtenderParseFunctions.kt b/src/main/kotlin/io/ExtenderParseFunctions.kt index 914f1a5..3598d7c 100644 --- a/src/main/kotlin/io/ExtenderParseFunctions.kt +++ b/src/main/kotlin/io/ExtenderParseFunctions.kt @@ -29,7 +29,7 @@ import core.routing.Extender * @throws ParseException if the label is not recognized */ @Throws(ParseException::class) -fun parseInterdomainExtender(label: String, lineNumber: Int = 0): Extender { +fun parseInterdomainExtender(label: String, lineNumber: Int): Extender { return when (label.toLowerCase()) { "r+" -> PeerplusExtender @@ -40,4 +40,8 @@ fun parseInterdomainExtender(label: String, lineNumber: Int = 0): Extender throw ParseException("Extender label `$label` was not recognized: " + "must be either R+, C, R, P, or S", lineNumber) } +} + +fun parseInterdomainExtender(label: String): Extender { + return parseInterdomainExtender(label, lineNumber = 0) } \ No newline at end of file diff --git a/src/main/kotlin/simulation/StubDB.kt b/src/main/kotlin/simulation/StubDB.kt new file mode 100644 index 0000000..19236da --- /dev/null +++ b/src/main/kotlin/simulation/StubDB.kt @@ -0,0 +1,89 @@ +package simulation + +import core.routing.* +import io.ParseException +import io.StubParser +import java.util.* +import kotlin.collections.ArrayList + +/** + * Created on 31-08-2017 + * + * @author David Fialho + * + * @property topology the topology missing the stubs in this database + * @property parser the parser used to parse the stubs + * @property parseExtender function to convert a string label into an extender + */ +class StubDB( + private val topology: Topology, + private val parser: StubParser, + private val parseExtender: (String) -> Extender + +): StubParser.Handler { + + /** + * Stores the ID of the stub to get from the DB. + */ + private var stubToGet: NodeID = -1 + + /** + * Stores the links of the stub that is to be obtained. + */ + private val stubLinks = ArrayList, Extender>>() + + /** + * Obtains the stub node with id [stubID]. + * + * @param stubID the ID of the stub to get + * @param protocol the protocol to assign to the stub + * @return the stub node initialized with its in-neighbors + * @throws ParseException if the input file defined a neighbor not included in the topology or if it includes a + * label that is not recognized. + */ + @Throws(ParseException::class) + fun getStub(stubID: NodeID, protocol: Protocol): Node? { + + stubToGet = stubID + parser.parse(this) + + // Return empty optional if the stub was not found + if (stubLinks.isEmpty()) return null + + // Create stub node + val stub = Node(stubID, protocol) + stubLinks.forEach { (neighbor, extender) -> stub.addInNeighbor(neighbor, extender) } + + // Stub links are no longer required + stubLinks.clear() + + // Reset the parser so that it can be parsed again + parser.reset() + + return stub + } + + /** + * Invoked when a new stub item is found. + * + * @param id the ID of the stub + * @param inNeighbor the ID of the stub's in-neighbor + * @param label the label of the extender associated with the link between the neighbor and the stub + * @param currentLine line number where the stub link was parsed + * @throws ParseException if the neighbor was not included in the topology or if it includes a + * label that is not recognized. + */ + @Throws(ParseException::class) + override fun onStubLink(id: NodeID, inNeighbor: NodeID, label: String, currentLine: Int) { + + // Consider only the stub that we want to obtain + if (id != stubToGet) return + + val node = topology[inNeighbor] ?: + throw ParseException("Neighbor ID `$inNeighbor` was not found in the topology", currentLine) + val extender = parseExtender(label) + + // Add neighbor and extender to list of stub links corresponding to the stub we want to get + stubLinks.add(Pair(node, extender)) + } +} \ No newline at end of file diff --git a/src/test/kotlin/simulation/StubDBTest.kt b/src/test/kotlin/simulation/StubDBTest.kt new file mode 100644 index 0000000..d365d30 --- /dev/null +++ b/src/test/kotlin/simulation/StubDBTest.kt @@ -0,0 +1,96 @@ +package simulation + +import bgp.BGP +import bgp.BGPRoute +import bgp.policies.interdomain.CustomerExtender +import bgp.policies.interdomain.PeerExtender +import bgp.policies.interdomain.ProviderExtender +import core.routing.Extender +import core.routing.NodeID +import core.routing.TopologyBuilder +import io.StubParser +import io.parseInterdomainExtender +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.nullValue +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.given +import org.jetbrains.spek.api.dsl.it +import org.jetbrains.spek.api.dsl.on +import java.io.StringReader +import org.hamcrest.Matchers.`is` as Is + +/** + * Created on 31-08-2017 + * + * @author David Fialho + */ +object StubDBTest: Spek({ + + fun lines(vararg lines: String): String = lines.joinToString("\n") + + given("file with single lines `1 | 2 | C`, `1 | 3 | R`, `1 | 4 | P`") { + + val fileContent = lines( + "1 | 2 | C", + "1 | 3 | R", + "1 | 4 | P" + ) + + val topology = TopologyBuilder() + .addNode(2, BGP()) + .addNode(3, BGP()) + .addNode(4, BGP()) + .build() + + on("getting stub with ID 1") { + + val stub = StubParser(StringReader(fileContent)).use { + StubDB(topology, it, ::parseInterdomainExtender).getStub(stubID = 1, protocol = BGP())!! + } + + it("obtained a stub with three in-neighbors") { + assertThat(stub.inNeighbors.size, Is(3)) + } + + val inNeighborsIDs = stub.inNeighbors.map { it.node.id }.sorted() + val inNeighborsExtenders: Map> = stub.inNeighbors.map { it.node.id to it.extender }.toMap() + + it("obtained a stub with in-neighbor 2") { + assertThat(2 in inNeighborsIDs, Is(true)) + } + + it("obtained a stub with customer extender from neighbor 2") { + assertThat(inNeighborsExtenders[2], Is(CustomerExtender as Extender)) + } + + it("obtained a stub with in-neighbor 3") { + assertThat(3 in inNeighborsIDs, Is(true)) + } + + it("obtained a stub with peer extender from neighbor 3") { + assertThat(inNeighborsExtenders[3], Is(PeerExtender as Extender)) + } + + it("obtained a stub with in-neighbor 4") { + assertThat(4 in inNeighborsIDs, Is(true)) + } + + it("obtained a stub with provider extender from neighbor 4") { + assertThat(inNeighborsExtenders[4], Is(ProviderExtender as Extender)) + } + + } + + on("getting stub with ID 3") { + + val stub = StubParser(StringReader(fileContent)).use { + StubDB(topology, it, ::parseInterdomainExtender).getStub(stubID = 3, protocol = BGP()) + } + + it("did not obtain a stub") { + assertThat(stub, Is(nullValue())) + } + + } + } +}) \ No newline at end of file From 19f63a014857c1e9d0a79ee390c63923b83f45fc Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 31 Aug 2017 19:25:05 +0100 Subject: [PATCH 10/19] Add route typed defined to repetition runner --- src/main/kotlin/io/InterdomainTopologyReader.kt | 2 +- src/main/kotlin/io/TopologyReader.kt | 5 +++-- src/main/kotlin/io/TopologyReaderHandler.kt | 10 ++++++---- src/main/kotlin/simulation/RepetitionRunner.kt | 9 +++++---- src/main/kotlin/ui/Application.kt | 9 +++++---- src/main/kotlin/ui/DummyApplication.kt | 9 +++++---- src/main/kotlin/ui/cli/CLIApplication.kt | 11 ++++++----- 7 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/io/InterdomainTopologyReader.kt b/src/main/kotlin/io/InterdomainTopologyReader.kt index fea8323..e85d7ba 100644 --- a/src/main/kotlin/io/InterdomainTopologyReader.kt +++ b/src/main/kotlin/io/InterdomainTopologyReader.kt @@ -14,7 +14,7 @@ import java.io.* * * @author David Fialho */ -class InterdomainTopologyReader(reader: Reader): TopologyReader, Closeable, Handler { +class InterdomainTopologyReader(reader: Reader): TopologyReader, Closeable, Handler { /** * Provides option to create a reader with a file object. diff --git a/src/main/kotlin/io/TopologyReader.kt b/src/main/kotlin/io/TopologyReader.kt index 65776da..259d84c 100644 --- a/src/main/kotlin/io/TopologyReader.kt +++ b/src/main/kotlin/io/TopologyReader.kt @@ -1,5 +1,6 @@ package io +import core.routing.Route import core.routing.Topology /** @@ -7,7 +8,7 @@ import core.routing.Topology * * @author David Fialho */ -interface TopologyReader { +interface TopologyReader { - fun read(): Topology<*> + fun read(): Topology } \ No newline at end of file diff --git a/src/main/kotlin/io/TopologyReaderHandler.kt b/src/main/kotlin/io/TopologyReaderHandler.kt index 2f12d35..c519720 100644 --- a/src/main/kotlin/io/TopologyReaderHandler.kt +++ b/src/main/kotlin/io/TopologyReaderHandler.kt @@ -1,5 +1,7 @@ package io +import bgp.BGPRoute +import core.routing.Route import core.routing.Topology import java.io.File import java.io.FileReader @@ -11,7 +13,7 @@ import java.io.Reader * * @author David Fialho */ -sealed class TopologyReaderHandler { +sealed class TopologyReaderHandler { /** * Reads the topology file associated with the handler on a new topology reader and then closes it down correctly @@ -22,14 +24,14 @@ sealed class TopologyReaderHandler { * @throws ParseException - if a topology object can not be created due to incorrect representation */ @Throws(IOException::class, ParseException::class) - abstract fun read(): Topology<*> + abstract fun read(): Topology } /** * Handler for InterdomainTopologyReader. */ -class InterdomainTopologyReaderHandler(private val reader: Reader): TopologyReaderHandler() { +class InterdomainTopologyReaderHandler(private val reader: Reader): TopologyReaderHandler() { constructor(topologyFile: File): this(FileReader(topologyFile)) @@ -41,7 +43,7 @@ class InterdomainTopologyReaderHandler(private val reader: Reader): TopologyRead * @throws ParseException - if a topology object can not be created due to incorrect representation */ @Throws(IOException::class, ParseException::class) - override fun read(): Topology<*> { + override fun read(): Topology { InterdomainTopologyReader(reader).use { return it.read() diff --git a/src/main/kotlin/simulation/RepetitionRunner.kt b/src/main/kotlin/simulation/RepetitionRunner.kt index adedb16..43041d9 100644 --- a/src/main/kotlin/simulation/RepetitionRunner.kt +++ b/src/main/kotlin/simulation/RepetitionRunner.kt @@ -2,6 +2,7 @@ package simulation import core.routing.Node import core.routing.NodeID +import core.routing.Route import core.routing.Topology import core.simulator.DelayGenerator import core.simulator.Engine @@ -14,9 +15,9 @@ import java.io.File * * @author David Fialho */ -class RepetitionRunner( +class RepetitionRunner( private val topologyFile: File, - private val topologyReader: TopologyReaderHandler, + private val topologyReader: TopologyReaderHandler, private val destination: NodeID, private val repetitions: Int, private val messageDelayGenerator: DelayGenerator @@ -34,11 +35,11 @@ class RepetitionRunner( */ override fun run(execution: Execution, application: Application) { - val topology: Topology<*> = application.loadTopology(topologyFile, topologyReader) { + val topology: Topology = application.loadTopology(topologyFile, topologyReader) { topologyReader.read() } - val destination: Node<*> = application.findDestination(destination) { + val destination: Node = application.findDestination(destination) { topology[destination] } diff --git a/src/main/kotlin/ui/Application.kt b/src/main/kotlin/ui/Application.kt index 5eaa072..e28e71d 100644 --- a/src/main/kotlin/ui/Application.kt +++ b/src/main/kotlin/ui/Application.kt @@ -2,6 +2,7 @@ package ui import core.routing.Node import core.routing.NodeID +import core.routing.Route import core.routing.Topology import io.TopologyReaderHandler import java.io.File @@ -22,10 +23,10 @@ interface Application { * @param topologyReader the reader used to load the topology into memory * @param loadBlock the code block to load the topology. */ - fun loadTopology(topologyFile: File, topologyReader: TopologyReaderHandler, - loadBlock: () -> Topology<*>): Topology<*> + fun loadTopology(topologyFile: File, topologyReader: TopologyReaderHandler, + loadBlock: () -> Topology): Topology - fun findDestination(destinationID: NodeID, block: () -> Node<*>?): Node<*> + fun findDestination(destinationID: NodeID, block: () -> Node?): Node /** * Invoked while executing each execution. @@ -35,7 +36,7 @@ interface Application { * @param seed the seed of the message delay generator used for the execution * @param executeBlock the code block that performs one execution */ - fun execute(executionID: Int, destination: Node<*>, seed: Long, executeBlock: () -> Unit) + fun execute(executionID: Int, destination: Node, seed: Long, executeBlock: () -> Unit) /** * Invoked during a run. diff --git a/src/main/kotlin/ui/DummyApplication.kt b/src/main/kotlin/ui/DummyApplication.kt index cb37eba..39ad735 100644 --- a/src/main/kotlin/ui/DummyApplication.kt +++ b/src/main/kotlin/ui/DummyApplication.kt @@ -2,6 +2,7 @@ package ui import core.routing.Node import core.routing.NodeID +import core.routing.Route import core.routing.Topology import io.TopologyReaderHandler import java.io.File @@ -15,12 +16,12 @@ object DummyApplication: Application { override fun launch(args: Array) = Unit - override fun loadTopology(topologyFile: File, topologyReader: TopologyReaderHandler, - loadBlock: () -> Topology<*>): Topology<*> = loadBlock() + override fun loadTopology(topologyFile: File, topologyReader: TopologyReaderHandler, + loadBlock: () -> Topology): Topology = loadBlock() - override fun findDestination(destinationID: NodeID, block: () -> Node<*>?): Node<*> = block()!! + override fun findDestination(destinationID: NodeID, block: () -> Node?): Node = block()!! - override fun execute(executionID: Int, destination: Node<*>, seed: Long, executeBlock: () -> Unit) = Unit + override fun execute(executionID: Int, destination: Node, seed: Long, executeBlock: () -> Unit) = Unit override fun run(runBlock: () -> Unit) = Unit diff --git a/src/main/kotlin/ui/cli/CLIApplication.kt b/src/main/kotlin/ui/cli/CLIApplication.kt index baf0070..d951408 100644 --- a/src/main/kotlin/ui/cli/CLIApplication.kt +++ b/src/main/kotlin/ui/cli/CLIApplication.kt @@ -2,6 +2,7 @@ package ui.cli import core.routing.Node import core.routing.NodeID +import core.routing.Route import core.routing.Topology import io.ParseException import io.TopologyReaderHandler @@ -44,8 +45,8 @@ object CLIApplication: Application { * @param topologyReader the reader used to load the topology into memory * @param loadBlock the code block to load the topology. */ - override fun loadTopology(topologyFile: File, topologyReader: TopologyReaderHandler, - loadBlock: () -> Topology<*>): Topology<*> { + override fun loadTopology(topologyFile: File, topologyReader: TopologyReaderHandler, + loadBlock: () -> Topology): Topology { try { console.info("Topology file: ${topologyFile.path}.") @@ -77,8 +78,8 @@ object CLIApplication: Application { * @param destinationID the destination ID * @param block the block of code to find the destination */ - override fun findDestination(destinationID: NodeID, block: () -> Node<*>?): Node<*> { - val destination: Node<*>? = block() + override fun findDestination(destinationID: NodeID, block: () -> Node?): Node { + val destination: Node? = block() if (destination == null) { console.error("Destination `$destinationID` was not found.") @@ -96,7 +97,7 @@ object CLIApplication: Application { * @param seed the seed of the message delay generator used for the execution * @param executeBlock the code block that performs one execution */ - override fun execute(executionID: Int, destination: Node<*>, seed: Long, executeBlock: () -> Unit) { + override fun execute(executionID: Int, destination: Node, seed: Long, executeBlock: () -> Unit) { console.info("Executing $executionID (destination=${destination.id} and seed=$seed)... ", inline = true) val (duration, _) = timer { From 03c238504c3b2ebe93194c78b9a14ca7666c2c52 Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 31 Aug 2017 19:43:29 +0100 Subject: [PATCH 11/19] Add support for stubs to runner and CLI application --- .../kotlin/simulation/RepetitionRunner.kt | 9 +- src/main/kotlin/simulation/StubDB.kt | 34 ++++--- src/main/kotlin/ui/cli/CLIApplication.kt | 15 ++- .../kotlin/ui/cli/InputArgumentsParser.kt | 34 +++++-- src/test/kotlin/simulation/StubDBTest.kt | 96 ------------------- 5 files changed, 61 insertions(+), 127 deletions(-) delete mode 100644 src/test/kotlin/simulation/StubDBTest.kt diff --git a/src/main/kotlin/simulation/RepetitionRunner.kt b/src/main/kotlin/simulation/RepetitionRunner.kt index 43041d9..43570d4 100644 --- a/src/main/kotlin/simulation/RepetitionRunner.kt +++ b/src/main/kotlin/simulation/RepetitionRunner.kt @@ -18,9 +18,10 @@ import java.io.File class RepetitionRunner( private val topologyFile: File, private val topologyReader: TopologyReaderHandler, - private val destination: NodeID, + private val destinationID: NodeID, private val repetitions: Int, - private val messageDelayGenerator: DelayGenerator + private val messageDelayGenerator: DelayGenerator, + private val stubDB: StubDB? ): Runner { @@ -39,8 +40,8 @@ class RepetitionRunner( topologyReader.read() } - val destination: Node = application.findDestination(destination) { - topology[destination] + val destination: Node = application.findDestination(destinationID) { + topology[destinationID] ?: stubDB?.getStub(destinationID, topology) } Engine.messageDelayGenerator = messageDelayGenerator diff --git a/src/main/kotlin/simulation/StubDB.kt b/src/main/kotlin/simulation/StubDB.kt index 19236da..da63496 100644 --- a/src/main/kotlin/simulation/StubDB.kt +++ b/src/main/kotlin/simulation/StubDB.kt @@ -3,6 +3,7 @@ package simulation import core.routing.* import io.ParseException import io.StubParser +import java.io.File import java.util.* import kotlin.collections.ArrayList @@ -11,21 +12,18 @@ import kotlin.collections.ArrayList * * @author David Fialho * - * @property topology the topology missing the stubs in this database - * @property parser the parser used to parse the stubs + * @param stubProtocol the protocol to assign to the stub * @property parseExtender function to convert a string label into an extender */ class StubDB( - private val topology: Topology, - private val parser: StubParser, + val stubsFile: File, + private val stubProtocol: Protocol, private val parseExtender: (String) -> Extender ): StubParser.Handler { - /** - * Stores the ID of the stub to get from the DB. - */ - private var stubToGet: NodeID = -1 + private var topology: Topology? = null + private var stubID: NodeID = -1 /** * Stores the links of the stub that is to be obtained. @@ -36,30 +34,30 @@ class StubDB( * Obtains the stub node with id [stubID]. * * @param stubID the ID of the stub to get - * @param protocol the protocol to assign to the stub + * @param topology the topology missing the stubs in this database * @return the stub node initialized with its in-neighbors * @throws ParseException if the input file defined a neighbor not included in the topology or if it includes a * label that is not recognized. */ @Throws(ParseException::class) - fun getStub(stubID: NodeID, protocol: Protocol): Node? { + fun getStub(stubID: NodeID, topology: Topology): Node? { - stubToGet = stubID - parser.parse(this) + StubParser.useFile(stubsFile).use { + this.stubID = stubID + this.topology = topology + it.parse(this) + } // Return empty optional if the stub was not found if (stubLinks.isEmpty()) return null // Create stub node - val stub = Node(stubID, protocol) + val stub = Node(stubID, stubProtocol) stubLinks.forEach { (neighbor, extender) -> stub.addInNeighbor(neighbor, extender) } // Stub links are no longer required stubLinks.clear() - // Reset the parser so that it can be parsed again - parser.reset() - return stub } @@ -77,9 +75,9 @@ class StubDB( override fun onStubLink(id: NodeID, inNeighbor: NodeID, label: String, currentLine: Int) { // Consider only the stub that we want to obtain - if (id != stubToGet) return + if (id != stubID) return - val node = topology[inNeighbor] ?: + val node = topology?.get(inNeighbor) ?: throw ParseException("Neighbor ID `$inNeighbor` was not found in the topology", currentLine) val extender = parseExtender(label) diff --git a/src/main/kotlin/ui/cli/CLIApplication.kt b/src/main/kotlin/ui/cli/CLIApplication.kt index d951408..682bcc0 100644 --- a/src/main/kotlin/ui/cli/CLIApplication.kt +++ b/src/main/kotlin/ui/cli/CLIApplication.kt @@ -79,7 +79,20 @@ object CLIApplication: Application { * @param block the block of code to find the destination */ override fun findDestination(destinationID: NodeID, block: () -> Node?): Node { - val destination: Node? = block() + + val destination= try { + block() + + } catch (exception: ParseException) { + console.error("Failed to parse stubs file.") + console.error("Cause: ${exception.message ?: "No information available"}") + exitProcess(1) + + } catch (exception: IOException) { + console.error("Failed to read stubs file due to IO error.") + console.error("Cause: ${exception.message ?: "No information available"}") + exitProcess(2) + } if (destination == null) { console.error("Destination `$destinationID` was not found.") diff --git a/src/main/kotlin/ui/cli/InputArgumentsParser.kt b/src/main/kotlin/ui/cli/InputArgumentsParser.kt index 3fc6260..f2541a5 100644 --- a/src/main/kotlin/ui/cli/InputArgumentsParser.kt +++ b/src/main/kotlin/ui/cli/InputArgumentsParser.kt @@ -1,12 +1,16 @@ package ui.cli +import bgp.BGP import core.simulator.RandomDelayGenerator import io.InterdomainTopologyReaderHandler +import io.StubParser +import io.parseInterdomainExtender import org.apache.commons.cli.CommandLine import org.apache.commons.cli.DefaultParser import org.apache.commons.cli.Options import simulation.* import java.io.File +import java.util.* /** * Created on 30-08-2017 @@ -25,6 +29,7 @@ class InputArgumentsParser { private val MAX_DELAY = "maxdelay" private val THRESHOLD = "threshold" private val SEED = "seed" + private val STUBS = "stubs" private val options = Options() @@ -38,6 +43,7 @@ class InputArgumentsParser { options.addOption(MAX_DELAY, true, "maximum message delay (inclusive)") options.addOption("th", THRESHOLD, true, "threshold value") options.addOption(SEED, true, "first seed used for generate message delays") + options.addOption(STUBS, true, "path to stubs file") } @Throws(InputArgumentsException::class) @@ -45,12 +51,13 @@ class InputArgumentsParser { DefaultParser().parse(options, args).let { - val topologyFile = getFile(it, option = TOPOLOGY_FILE) + val topologyFile = getFile(it, option = TOPOLOGY_FILE).get() val destination = getNonNegativeInteger(it, option = DESTINATION) val repetitions = getPositiveInteger(it, option = REPETITIONS, default = 1) val reportDirectory = getDirectory(it, option = REPORT_DIRECTORY, default = File(System.getProperty("user.dir"))) val threshold = getPositiveInteger(it, option = THRESHOLD, default = 1_000_000) val seed = getLong(it, option = SEED, default = System.currentTimeMillis()) + val stubsFile = getFile(it, option = STUBS, default = Optional.empty()) val minDelay = getPositiveInteger(it, option = MIN_DELAY, default = 1) val maxDelay = getPositiveInteger(it, option = MAX_DELAY, default = 1) @@ -66,7 +73,20 @@ class InputArgumentsParser { val topologyReader = InterdomainTopologyReaderHandler(topologyFile) val messageDelayGenerator = RandomDelayGenerator.with(minDelay, maxDelay, seed) - val runner = RepetitionRunner(topologyFile, topologyReader, destination, repetitions, messageDelayGenerator) + val stubDB = if (stubsFile.isPresent) { + StubDB(stubsFile.get(), BGP(), ::parseInterdomainExtender) + } else { + null + } + + val runner = RepetitionRunner( + topologyFile, + topologyReader, + destination, + repetitions, + messageDelayGenerator, + stubDB + ) val execution = SimpleAdvertisementExecution(threshold).apply { dataCollectors.add(BasicDataCollector(reportFile)) } @@ -74,18 +94,16 @@ class InputArgumentsParser { return Pair(runner, execution) } } - @Throws(InputArgumentsException::class) - private fun getFile(commandLine: CommandLine, option: String, default: File? = null): File { + private fun getFile(commandLine: CommandLine, option: String, default: Optional? = null): Optional { verifyOption(commandLine, option, default) val value = commandLine.getOptionValue(option) - val file = if (value != null) File(value) else default!! // See note below - + val file = if (value != null) Optional.of(File(value)) else default!! // See note below // Note: the verifyOption method would throw exception if the option was ot defined and default was null - if (!file.isFile) { - throw InputArgumentsException("The file specified for `$option` does not exist: ${file.path}") + if (file.isPresent && !file.get().isFile) { + throw InputArgumentsException("The file specified for `$option` does not exist: ${file.get().path}") } return file diff --git a/src/test/kotlin/simulation/StubDBTest.kt b/src/test/kotlin/simulation/StubDBTest.kt deleted file mode 100644 index d365d30..0000000 --- a/src/test/kotlin/simulation/StubDBTest.kt +++ /dev/null @@ -1,96 +0,0 @@ -package simulation - -import bgp.BGP -import bgp.BGPRoute -import bgp.policies.interdomain.CustomerExtender -import bgp.policies.interdomain.PeerExtender -import bgp.policies.interdomain.ProviderExtender -import core.routing.Extender -import core.routing.NodeID -import core.routing.TopologyBuilder -import io.StubParser -import io.parseInterdomainExtender -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.nullValue -import org.jetbrains.spek.api.Spek -import org.jetbrains.spek.api.dsl.given -import org.jetbrains.spek.api.dsl.it -import org.jetbrains.spek.api.dsl.on -import java.io.StringReader -import org.hamcrest.Matchers.`is` as Is - -/** - * Created on 31-08-2017 - * - * @author David Fialho - */ -object StubDBTest: Spek({ - - fun lines(vararg lines: String): String = lines.joinToString("\n") - - given("file with single lines `1 | 2 | C`, `1 | 3 | R`, `1 | 4 | P`") { - - val fileContent = lines( - "1 | 2 | C", - "1 | 3 | R", - "1 | 4 | P" - ) - - val topology = TopologyBuilder() - .addNode(2, BGP()) - .addNode(3, BGP()) - .addNode(4, BGP()) - .build() - - on("getting stub with ID 1") { - - val stub = StubParser(StringReader(fileContent)).use { - StubDB(topology, it, ::parseInterdomainExtender).getStub(stubID = 1, protocol = BGP())!! - } - - it("obtained a stub with three in-neighbors") { - assertThat(stub.inNeighbors.size, Is(3)) - } - - val inNeighborsIDs = stub.inNeighbors.map { it.node.id }.sorted() - val inNeighborsExtenders: Map> = stub.inNeighbors.map { it.node.id to it.extender }.toMap() - - it("obtained a stub with in-neighbor 2") { - assertThat(2 in inNeighborsIDs, Is(true)) - } - - it("obtained a stub with customer extender from neighbor 2") { - assertThat(inNeighborsExtenders[2], Is(CustomerExtender as Extender)) - } - - it("obtained a stub with in-neighbor 3") { - assertThat(3 in inNeighborsIDs, Is(true)) - } - - it("obtained a stub with peer extender from neighbor 3") { - assertThat(inNeighborsExtenders[3], Is(PeerExtender as Extender)) - } - - it("obtained a stub with in-neighbor 4") { - assertThat(4 in inNeighborsIDs, Is(true)) - } - - it("obtained a stub with provider extender from neighbor 4") { - assertThat(inNeighborsExtenders[4], Is(ProviderExtender as Extender)) - } - - } - - on("getting stub with ID 3") { - - val stub = StubParser(StringReader(fileContent)).use { - StubDB(topology, it, ::parseInterdomainExtender).getStub(stubID = 3, protocol = BGP()) - } - - it("did not obtain a stub") { - assertThat(stub, Is(nullValue())) - } - - } - } -}) \ No newline at end of file From 8edd47f2196dcdba8d630052086b2fb5e227ec54 Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 31 Aug 2017 19:57:37 +0100 Subject: [PATCH 12/19] Print total run duration --- src/main/kotlin/ui/cli/CLIApplication.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/ui/cli/CLIApplication.kt b/src/main/kotlin/ui/cli/CLIApplication.kt index 682bcc0..4824e8a 100644 --- a/src/main/kotlin/ui/cli/CLIApplication.kt +++ b/src/main/kotlin/ui/cli/CLIApplication.kt @@ -126,8 +126,10 @@ object CLIApplication: Application { try { console.info("Running...") - runBlock() - console.info("Finished run") + val (duration, _) = timer { + runBlock() + } + console.info("Finished run in $duration in seconds") } catch (exception: IOException) { console.error("Failed to report results due to an IO error.") From 43133cb7f1773d2b5d7bd0773c94a3ee6efe90fb Mon Sep 17 00:00:00 2001 From: David Fialho Date: Fri, 1 Sep 2017 08:34:46 +0100 Subject: [PATCH 13/19] Add SSBGP version 2 implementation --- src/main/kotlin/bgp/SSBGP.kt | 30 +- .../bgp/SSBGP2WithShortestPathRoutingTests.kt | 272 ++++++++++++++++++ 2 files changed, 297 insertions(+), 5 deletions(-) create mode 100644 src/test/kotlin/bgp/SSBGP2WithShortestPathRoutingTests.kt diff --git a/src/main/kotlin/bgp/SSBGP.kt b/src/main/kotlin/bgp/SSBGP.kt index 69ed21a..c12dde0 100644 --- a/src/main/kotlin/bgp/SSBGP.kt +++ b/src/main/kotlin/bgp/SSBGP.kt @@ -25,6 +25,8 @@ abstract class BaseSSBGP(mrai: Time = 0, routingTable: RoutingTable): return } + val prevSelectedRoute = routingTable.getSelectedRoute() + // Since a loop routing was detected, the new route via the sender node is surely invalid // Set the route via the sender as invalid @@ -33,7 +35,7 @@ abstract class BaseSSBGP(mrai: Time = 0, routingTable: RoutingTable): wasSelectedRouteUpdated = wasSelectedRouteUpdated || updated val alternativeRoute = routingTable.getSelectedRoute() - if (isRecurrent(node, route, alternativeRoute)) { + if (isRecurrent(node, route, alternativeRoute, prevSelectedRoute)) { disableNeighbor(sender) BGPNotifier.notifyDetect(DetectNotification(node, route, alternativeRoute, sender)) } @@ -43,7 +45,8 @@ abstract class BaseSSBGP(mrai: Time = 0, routingTable: RoutingTable): * Checks if the routing loop detected is recurrent. * Subclasses must implement this method to define the detection condition. */ - abstract fun isRecurrent(node: Node, learnedRoute: BGPRoute, alternativeRoute: BGPRoute): Boolean + protected abstract fun isRecurrent(node: Node, learnedRoute: BGPRoute, + alternativeRoute: BGPRoute, prevSelectedRoute: BGPRoute): Boolean /** * Enables the specified neighbor. @@ -81,7 +84,9 @@ abstract class BaseSSBGP(mrai: Time = 0, routingTable: RoutingTable): class SSBGP(mrai: Time = 0, routingTable: RoutingTable = RoutingTable.empty(BGPRoute.invalid())) : BaseSSBGP(mrai, routingTable) { - override fun isRecurrent(node: Node, learnedRoute: BGPRoute, alternativeRoute: BGPRoute): Boolean { + override fun isRecurrent(node: Node, learnedRoute: BGPRoute, alternativeRoute: BGPRoute, + prevSelectedRoute: BGPRoute): Boolean { + return learnedRoute.localPref > alternativeRoute.localPref } } @@ -93,8 +98,23 @@ class SSBGP(mrai: Time = 0, routingTable: RoutingTable = RoutingTable. class ISSBGP(mrai: Time = 0, routingTable: RoutingTable = RoutingTable.empty(BGPRoute.invalid())) : BaseSSBGP(mrai, routingTable) { - override fun isRecurrent(node: Node, learnedRoute: BGPRoute, alternativeRoute: BGPRoute): Boolean { + override fun isRecurrent(node: Node, learnedRoute: BGPRoute, alternativeRoute: BGPRoute, + prevSelectedRoute: BGPRoute): Boolean { + return learnedRoute.localPref > alternativeRoute.localPref && alternativeRoute.asPath == learnedRoute.asPath.subPathBefore(node) } -} \ No newline at end of file +} + +/** + * SS-BGP version 2 Protocol: it uses a more generic detection condition than version 1. + */ +class SSBGP2(mrai: Time = 0, routingTable: RoutingTable = RoutingTable.empty(BGPRoute.invalid())) + : BaseSSBGP(mrai, routingTable) { + + override fun isRecurrent(node: Node, learnedRoute: BGPRoute, alternativeRoute: BGPRoute, + prevSelectedRoute: BGPRoute): Boolean { + + return alternativeRoute.localPref < prevSelectedRoute.localPref + } +} diff --git a/src/test/kotlin/bgp/SSBGP2WithShortestPathRoutingTests.kt b/src/test/kotlin/bgp/SSBGP2WithShortestPathRoutingTests.kt new file mode 100644 index 0000000..a0ed805 --- /dev/null +++ b/src/test/kotlin/bgp/SSBGP2WithShortestPathRoutingTests.kt @@ -0,0 +1,272 @@ +package bgp + +import core.routing.pathOf +import core.simulator.Engine +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.`is` as Is +import org.hamcrest.Matchers.nullValue +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.given +import org.jetbrains.spek.api.dsl.it +import org.jetbrains.spek.api.dsl.on +import testing.* +import testing.bgp.pathOf + +/** + * Created on 01-09-2017 + * + * @author David Fialho + */ +object SSBGP2WithShortestPathRoutingTests: Spek({ + + given("topology with a single link from 2 to 1 with cost 10") { + + val topology = bgpTopology { + node { 1 deploying SSBGP2() } + node { 2 deploying SSBGP2() } + + link { 2 to 1 withCost 10 } + } + + afterEachTest { + Engine.scheduler.reset() + topology.reset() + } + + val node1 = topology[1]!! + val node2 = topology[2]!! + val protocol1 = node1.protocol as SSBGP2 + val protocol2 = node2.protocol as SSBGP2 + + on("simulating with node 1 as the destination") { + + val terminated = Engine.simulate(topology, node1, threshold = 1000) + + it("terminated") { + assertThat(terminated, Is(true)) + } + + it("finishes with node 1 selecting self route") { + assertThat(protocol1.routingTable.getSelectedRoute(), Is(BGPRoute.self())) + } + + it("finishes with node 1 selecting route via himself") { + assertThat(protocol1.routingTable.getSelectedNeighbor(), Is(node1)) + } + + it("finishes with node 2 selecting route with LOCAL-PREF=10 and AS-PATH=[1]") { + assertThat(protocol2.routingTable.getSelectedRoute(), Is(BGPRoute.with(10, pathOf(node1)))) + } + + it("finishes with node 2 selecting route via node 1") { + assertThat(protocol2.routingTable.getSelectedNeighbor(), Is(node1)) + } + } + + on("simulating with node 2 as the destination") { + + val terminated = Engine.simulate(topology, node2, threshold = 1000) + + it("terminated") { + assertThat(terminated, Is(true)) + } + + it("finishes with node 1 selecting an invalid route") { + assertThat(protocol1.routingTable.getSelectedRoute(), Is(BGPRoute.invalid())) + } + + it("finishes with node 1 selecting null neighbor") { + assertThat(protocol1.routingTable.getSelectedNeighbor(), Is(nullValue())) + } + + it("finishes with node 2 selecting self route") { + assertThat(protocol2.routingTable.getSelectedRoute(), Is(BGPRoute.self())) + } + + it("finishes with node 2 selecting route via himself") { + assertThat(protocol2.routingTable.getSelectedNeighbor(), Is(node2)) + } + } + } + + given("topology with 4 where three form a cycle and all three have a link for node 0") { + + val topology = bgpTopology { + node { 0 deploying SSBGP2() } + node { 1 deploying SSBGP2() } + node { 2 deploying SSBGP2() } + node { 3 deploying SSBGP2() } + + link { 1 to 0 withCost 0 } + link { 2 to 0 withCost 0 } + link { 3 to 0 withCost 0 } + link { 1 to 2 withCost 1 } + link { 2 to 3 withCost -1 } + link { 3 to 1 withCost 2 } + } + + afterEachTest { + Engine.scheduler.reset() + topology.reset() + } + + val node = topology.nodes.sortedBy { it.id } + val protocol = node.map { it.protocol as SSBGP2 } + + on("simulating with node 0 as the destination") { + + val terminated = Engine.simulate(topology, node[0], threshold = 1000) + + it("terminates") { + assertThat(terminated, Is(true)) + } + + it("finishes with node 1 selecting route with cost 0 via node 0") { + assertThat(protocol[1].routingTable.getSelectedRoute(), + Is(BGPRoute.with(localPref = 0, asPath = pathOf(0)))) + } + + it("finishes with node 2 selecting route with cost 0 via node 0") { + assertThat(protocol[2].routingTable.getSelectedRoute(), + Is(BGPRoute.with(localPref = 0, asPath = pathOf(0)))) + } + + it("finishes with node 3 selecting route with cost 2 via node 1") { + assertThat(protocol[3].routingTable.getSelectedRoute(), + Is(BGPRoute.with(localPref = 2, asPath = pathOf(0, 1)))) + } + + it("finishes with link from 1 to 2 disabled") { + assertThat(protocol[1].routingTable.table.isEnabled(node[2]), Is(false)) + } + + it("finishes with link from 2 to 3 disabled") { + assertThat(protocol[2].routingTable.table.isEnabled(node[3]), Is(false)) + } + + it("finishes with link from 3 to 1 enabled") { + assertThat(protocol[3].routingTable.table.isEnabled(node[1]), Is(true)) + } + } + } + + given("topology with absorbent cycle") { + + val topology = bgpTopology { + node { 0 deploying SSBGP2() } + node { 1 deploying SSBGP2() } + node { 2 deploying SSBGP2() } + node { 3 deploying SSBGP2() } + + link { 1 to 0 withCost 0 } + link { 2 to 0 withCost 0 } + link { 3 to 0 withCost 0 } + link { 1 to 2 withCost -3 } + link { 2 to 3 withCost 1 } + link { 3 to 1 withCost 2 } + } + + afterEachTest { + Engine.scheduler.reset() + topology.reset() + } + + val node = topology.nodes.sortedBy { it.id } + val protocol = node.map { it.protocol as SSBGP2 } + + on("simulating with node 0 as the destination") { + + val terminated = Engine.simulate(topology, node[0], threshold = 1000) + + it("terminates") { + assertThat(terminated, Is(true)) + } + + it("finishes with node 1 selecting route with cost 0 via node 0") { + assertThat(protocol[1].routingTable.getSelectedRoute(), + Is(BGPRoute.with(localPref = 0, asPath = pathOf(0)))) + } + + it("finishes with node 2 selecting route with cost 3 via node 3") { + assertThat(protocol[2].routingTable.getSelectedRoute(), + Is(BGPRoute.with(localPref = 3, asPath = pathOf(0, 1, 3)))) + } + + it("finishes with node 3 selecting route with cost 2 via node 1") { + assertThat(protocol[3].routingTable.getSelectedRoute(), + Is(BGPRoute.with(localPref = 2, asPath = pathOf(0, 1)))) + } + + it("finishes with link from 1 to 2 enabled") { + assertThat(protocol[1].routingTable.table.isEnabled(node[2]), Is(true)) + } + + it("finishes with link from 2 to 3 enabled") { + assertThat(protocol[2].routingTable.table.isEnabled(node[3]), Is(true)) + } + + it("finishes with link from 3 to 1 enabled") { + assertThat(protocol[3].routingTable.table.isEnabled(node[1]), Is(true)) + } + } + } + + given("topology with non-absorbent cycle, but only one node has an external route") { + + val topology = bgpTopology { + node { 0 deploying SSBGP2() } + node { 1 deploying SSBGP2() } + node { 2 deploying SSBGP2() } + node { 3 deploying SSBGP2() } + + link { 1 to 0 withCost 0 } + link { 1 to 2 withCost -1 } + link { 2 to 3 withCost 1 } + link { 3 to 1 withCost 2 } + } + + afterEachTest { + Engine.scheduler.reset() + topology.reset() + } + + val node = topology.nodes.sortedBy { it.id } + val protocol = node.map { it.protocol as SSBGP2 } + + on("simulating with node 0 as the destination") { + + val terminated = Engine.simulate(topology, node[0], threshold = 1000) + + it("terminates") { + assertThat(terminated, Is(true)) + } + + it("finishes with node 1 selecting route with cost 0 via node 0") { + assertThat(protocol[1].routingTable.getSelectedRoute(), + Is(BGPRoute.with(localPref = 0, asPath = pathOf(0)))) + } + + it("finishes with node 2 selecting route with cost 3 via node 3") { + assertThat(protocol[2].routingTable.getSelectedRoute(), + Is(BGPRoute.with(localPref = 3, asPath = pathOf(0, 1, 3)))) + } + + it("finishes with node 3 selecting route with cost 2 via node 1") { + assertThat(protocol[3].routingTable.getSelectedRoute(), + Is(BGPRoute.with(localPref = 2, asPath = pathOf(0, 1)))) + } + + it("finishes with link from 1 to 2 enabled") { + assertThat(protocol[1].routingTable.table.isEnabled(node[2]), Is(true)) + } + + it("finishes with link from 2 to 3 enabled") { + assertThat(protocol[2].routingTable.table.isEnabled(node[3]), Is(true)) + } + + it("finishes with link from 3 to 1 enabled") { + assertThat(protocol[3].routingTable.table.isEnabled(node[1]), Is(true)) + } + } + } +}) \ No newline at end of file From 53afca32c7172693b366fecdf621652b455e81ad Mon Sep 17 00:00:00 2001 From: David Fialho Date: Fri, 1 Sep 2017 08:38:12 +0100 Subject: [PATCH 14/19] Have InterdomainTopologyReader support SS-BGP2 --- src/main/kotlin/io/InterdomainTopologyReader.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/io/InterdomainTopologyReader.kt b/src/main/kotlin/io/InterdomainTopologyReader.kt index e85d7ba..7e1f1ac 100644 --- a/src/main/kotlin/io/InterdomainTopologyReader.kt +++ b/src/main/kotlin/io/InterdomainTopologyReader.kt @@ -1,9 +1,6 @@ package io -import bgp.BGP -import bgp.BGPRoute -import bgp.ISSBGP -import bgp.SSBGP +import bgp.* import core.routing.* import io.TopologyParser.Handler import utils.toNonNegativeInt @@ -64,8 +61,10 @@ class InterdomainTopologyReader(reader: Reader): TopologyReader, Close "bgp" -> BGP(mrai) "ssbgp" -> SSBGP(mrai) "issbgp" -> ISSBGP(mrai) - else -> throw ParseException("Protocol label `$protocolLabel` was not recognized: supported labels are BGP, " + - "SSBGP, and ISSBGP", currentLine) + "ssbgp2" -> SSBGP2(mrai) + else -> throw ParseException( + "Protocol label `$protocolLabel` was not recognized: supported labels are BGP, " + + "SSBGP, ISSBGP, and SSBGP2", currentLine) } try { From ba05fb44eb6d8c8c55f92cfb17799631b551e929 Mon Sep 17 00:00:00 2001 From: David Fialho Date: Wed, 6 Sep 2017 16:30:28 +0100 Subject: [PATCH 15/19] Convert ParseException to InputArgumentsException --- src/main/kotlin/ui/cli/InputArgumentsParser.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/ui/cli/InputArgumentsParser.kt b/src/main/kotlin/ui/cli/InputArgumentsParser.kt index f2541a5..6ea47b9 100644 --- a/src/main/kotlin/ui/cli/InputArgumentsParser.kt +++ b/src/main/kotlin/ui/cli/InputArgumentsParser.kt @@ -3,11 +3,11 @@ package ui.cli import bgp.BGP import core.simulator.RandomDelayGenerator import io.InterdomainTopologyReaderHandler -import io.StubParser import io.parseInterdomainExtender import org.apache.commons.cli.CommandLine import org.apache.commons.cli.DefaultParser import org.apache.commons.cli.Options +import org.apache.commons.cli.ParseException import simulation.* import java.io.File import java.util.* @@ -49,7 +49,13 @@ class InputArgumentsParser { @Throws(InputArgumentsException::class) fun parse(args: Array): Pair { - DefaultParser().parse(options, args).let { + val commandLine = try { + DefaultParser().parse(options, args) + } catch (e: ParseException) { + throw InputArgumentsException(e.message.toString()) + } + + commandLine.let { val topologyFile = getFile(it, option = TOPOLOGY_FILE).get() val destination = getNonNegativeInteger(it, option = DESTINATION) From b44421ce9416be92c0fd2c2985d4980bb34a55e9 Mon Sep 17 00:00:00 2001 From: David Fialho Date: Wed, 6 Sep 2017 16:31:05 +0100 Subject: [PATCH 16/19] Have simulator exit with error code on every critical error --- src/main/kotlin/ui/cli/CLIApplication.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/ui/cli/CLIApplication.kt b/src/main/kotlin/ui/cli/CLIApplication.kt index 4824e8a..f1e9052 100644 --- a/src/main/kotlin/ui/cli/CLIApplication.kt +++ b/src/main/kotlin/ui/cli/CLIApplication.kt @@ -29,12 +29,14 @@ object CLIApplication: Application { runner.run(execution, this) } catch (e: InputArgumentsException) { - console.error("Input arguments are invalid.\n${e.message}.") - console.error("Cause: ${e.message ?: "No information available"}") + console.error("Input arguments are invalid.") + console.error("Cause: ${e.message ?: "No information available."}") + exitProcess(1) } catch (e: Exception){ console.error("Program was interrupted due to unexpected error.") - console.error("Cause: ${e.message ?: "No information available"}") + console.error("Cause: ${e.message ?: "No information available."}") + exitProcess(1) } } From 417fe2d231ebd6893c69112ce4f0d440a0741dbb Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 7 Sep 2017 10:36:52 +0100 Subject: [PATCH 17/19] Add ISS-BGP version 2 implementation --- src/main/kotlin/bgp/SSBGP.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/kotlin/bgp/SSBGP.kt b/src/main/kotlin/bgp/SSBGP.kt index c12dde0..ecec931 100644 --- a/src/main/kotlin/bgp/SSBGP.kt +++ b/src/main/kotlin/bgp/SSBGP.kt @@ -118,3 +118,18 @@ class SSBGP2(mrai: Time = 0, routingTable: RoutingTable = RoutingTable return alternativeRoute.localPref < prevSelectedRoute.localPref } } + +/** + * ISS-BGP version 2 Protocol: it uses the detection condition of SS-BGP2 and also checks if the tail of looping + * path matches the path of the alternative route. + */ +class ISSBGP2(mrai: Time = 0, routingTable: RoutingTable = RoutingTable.empty(BGPRoute.invalid())) + : BaseSSBGP(mrai, routingTable) { + + override fun isRecurrent(node: Node, learnedRoute: BGPRoute, alternativeRoute: BGPRoute, + prevSelectedRoute: BGPRoute): Boolean { + + return alternativeRoute.localPref < prevSelectedRoute.localPref && + alternativeRoute.asPath == learnedRoute.asPath.subPathBefore(node) + } +} From f079e05a6c8147ae913e4cfd0d00d0b9ceb1607b Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 7 Sep 2017 10:43:52 +0100 Subject: [PATCH 18/19] Add ISS-BGP2 support to InterdomainTopologyReader --- src/main/kotlin/io/InterdomainTopologyReader.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/io/InterdomainTopologyReader.kt b/src/main/kotlin/io/InterdomainTopologyReader.kt index 7e1f1ac..a46a539 100644 --- a/src/main/kotlin/io/InterdomainTopologyReader.kt +++ b/src/main/kotlin/io/InterdomainTopologyReader.kt @@ -62,9 +62,10 @@ class InterdomainTopologyReader(reader: Reader): TopologyReader, Close "ssbgp" -> SSBGP(mrai) "issbgp" -> ISSBGP(mrai) "ssbgp2" -> SSBGP2(mrai) + "issbgp2" -> ISSBGP2(mrai) else -> throw ParseException( "Protocol label `$protocolLabel` was not recognized: supported labels are BGP, " + - "SSBGP, ISSBGP, and SSBGP2", currentLine) + "SSBGP, ISSBGP, SSBGP2, and ISSBGP2", currentLine) } try { From 952d8dfcc2da58b311cb425384ffec823dbe50ad Mon Sep 17 00:00:00 2001 From: David Fialho Date: Thu, 7 Sep 2017 10:45:27 +0100 Subject: [PATCH 19/19] Change version to 1.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 57fba32..7c42f5e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ group 'ssbgp' -version '2.0-SNAPSHOT' +version '1.1-SNAPSHOT' buildscript { ext.kotlin_version = '1.1.4-3'