Include ODL's BGP to HC minimal distro 31/6631/8
authorMarek Gradzki <mgradzki@cisco.com>
Fri, 31 Mar 2017 09:24:24 +0000 (11:24 +0200)
committerMarek Gradzki <mgradzki@cisco.com>
Fri, 26 May 2017 11:05:06 +0000 (13:05 +0200)
Features:
- static bgp instance
- static peer registry (both remote & app peers supported)
- bgp operational state exposed via RESTCONF/NETCONF
- bgp-inet support
- postman collection with examples

TODOs:
- make bgp disabled by default in honeycomb.json (probably bgp should be build using profile
  or allow to filter yangs loaded by SchemaCtx, otherwise footprint will grow)
- update asciidocs for infra module
- SPI for VPP integration:
  - allow RIB changes listeners
  - or writers (that can reject changes) to mirror netconf/restconf design
- expose bgp server & peer cfg via netconf/restconf (currently read from json files)
- test support for all BGP extensions (flowspec BGP needs additional care)
- make bgp extensions fully configurable (probably requires changes in ODL)

Testing

1) start hc distro:
./infra/minimal-distribution/target/minimal-distribution-1.17.07-SNAPSHOT-hc/minimal-distribution-1.17.07-SNAPSHOT/honeycomb

2) run bgp-testtool:
java -jar ./target/bgp-testtool-0.6.3-Boron-SR3-executable.jar
 -ra 127.0.0.1:1790 -la 127.0.0.2:17900 -pr 2 -ll DEBUG --active true -as 65000 -mp true

then GET http://localhost:8183/restconf/operational/bgp-rib:bgp-rib

Change-Id: I156d6a1c79174279db8c8936c40ac1f28e14794c
Signed-off-by: Marek Gradzki <mgradzki@cisco.com>
27 files changed:
common/honeycomb-parent/pom.xml
infra/bgp_postman_collection.json [new file with mode: 0644]
infra/data-impl/pom.xml
infra/minimal-distribution/pom.xml
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/Main.java
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BGPDispatcherImplProvider.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BGPExtensionConsumerContextProvider.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BGPPeerRegistryProvider.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpExtensionsModule.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpModule.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpNettyThreadGroupProvider.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpRIBProvider.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpReaderFactoryProvider.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpReadersModule.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpServerProvider.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpWriterFactoryProvider.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpWritersModule.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/RIBExtensionConsumerContextProvider.java [new file with mode: 0644]
infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/cfgattrs/HoneycombConfiguration.java
infra/minimal-distribution/src/main/resources/honeycomb-minimal-resources/config/bgp-peers.json [new file with mode: 0644]
infra/minimal-distribution/src/main/resources/honeycomb-minimal-resources/config/honeycomb.json
infra/minimal-distribution/src/main/resources/honeycomb-minimal-resources/config/logback.xml
infra/minimal-distribution/src/test/java/io/fd/honeycomb/infra/distro/BaseMinimalDistributionTest.java
infra/minimal-distribution/src/test/resources/bgp-peers.json [new file with mode: 0644]
infra/minimal-distribution/src/test/resources/honeycomb.json
infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/write/BindingBrokerWriter.java [new file with mode: 0644]
infra/translate-utils/src/test/java/io/fd/honeycomb/translate/util/write/BindingBrokerWriterTest.java [new file with mode: 0644]

index c0efd8d..f6f65f4 100644 (file)
@@ -50,6 +50,7 @@
         <netconf.version>1.1.3-Boron-SR3</netconf.version>
         <restconf.version>1.4.3-Boron-SR3</restconf.version>
         <salGenerator.version>0.9.3-Boron-SR3</salGenerator.version>
+        <bgpcep.version>0.6.3-Boron-SR3</bgpcep.version>
         <salGeneratorPath>target/generated-sources/mdsal-binding</salGeneratorPath>
 
         <!-- dependencies -->
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
+            <dependency>
+                <groupId>org.opendaylight.bgpcep</groupId>
+                <artifactId>bgpcep-artifacts</artifactId>
+                <version>${bgpcep.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
 
             <!-- Utilities -->
             <dependency>
diff --git a/infra/bgp_postman_collection.json b/infra/bgp_postman_collection.json
new file mode 100644 (file)
index 0000000..1e7602a
--- /dev/null
@@ -0,0 +1,136 @@
+{
+       "id": "866862e9-5b18-cf86-e8a3-c121422e38e6",
+       "name": "Honeycomb BGP RESTCONF calls",
+       "description": "Examples of BGP configration based on \nhttp://docs.opendaylight.org/en/stable-boron/user-guide/bgp-user-guide.html",
+       "order": [
+               "aed706f7-b83c-93b2-552b-831c1576a0c6",
+               "830e5608-4b7e-fbcf-e1b6-057af48570a7",
+               "62045d77-ce19-7e74-468b-74843c20b5ce",
+               "2632240a-42ce-126e-11a5-b7f8e16a0a28",
+               "4f5f4d00-2bd6-6f23-5f19-d15130f7cdb8",
+               "3af97a8b-b9f3-7a20-6ce8-5d492954d3c2"
+       ],
+       "folders": [],
+       "timestamp": 1494477000331,
+       "owner": "567303",
+       "public": false,
+       "requests": [
+               {
+                       "id": "2632240a-42ce-126e-11a5-b7f8e16a0a28",
+                       "headers": "Content-Type: application/xml\nAuthorization: Basic YWRtaW46YWRtaW4=\n",
+                       "url": "http://localhost:8183/restconf/config/bgp-rib:application-rib/10.25.1.9/tables/bgp-types:ipv4-address-family/bgp-types:unicast-subsequent-address-family/bgp-inet:ipv4-routes",
+                       "preRequestScript": null,
+                       "pathVariables": {},
+                       "method": "POST",
+                       "data": [],
+                       "dataMode": "raw",
+                       "tests": null,
+                       "currentHelper": "normal",
+                       "helperAttributes": {},
+                       "time": 1494502752142,
+                       "name": "add ipv4unicast route",
+                       "description": "",
+                       "collectionId": "866862e9-5b18-cf86-e8a3-c121422e38e6",
+                       "responses": [],
+                       "rawModeData": "<ipv4-route xmlns=\"urn:opendaylight:params:xml:ns:yang:bgp-inet\">\n    <path-id>0</path-id>\n    <prefix>10.0.0.11/32</prefix>\n    <attributes>\n        <as-path></as-path>\n        <origin>\n            <value>igp</value>\n        </origin>\n        <local-pref>\n            <pref>100</pref>\n        </local-pref>\n        <ipv4-next-hop>\n            <global>10.11.1.1</global>\n        </ipv4-next-hop>\n    </attributes>\n</ipv4-route>"
+               },
+               {
+                       "id": "3af97a8b-b9f3-7a20-6ce8-5d492954d3c2",
+                       "headers": "Content-Type: application/xml\nAuthorization: Basic YWRtaW46YWRtaW4=\n",
+                       "url": "http://localhost:8183/restconf/operational/bgp-rib:bgp-rib/rib/hc-bgp-instance/peer/bgp:%2F%2F127.0.0.2/adj-rib-out/tables/bgp-types:ipv4-address-family/bgp-types:unicast-subsequent-address-family/bgp-inet:ipv4-routes",
+                       "preRequestScript": null,
+                       "pathVariables": {},
+                       "method": "GET",
+                       "data": [],
+                       "dataMode": "raw",
+                       "version": 2,
+                       "tests": null,
+                       "currentHelper": "normal",
+                       "helperAttributes": {},
+                       "time": 1495009639185,
+                       "name": "show 127.0.0.2 peer's adj-rib-out",
+                       "description": "",
+                       "collectionId": "866862e9-5b18-cf86-e8a3-c121422e38e6",
+                       "responses": [],
+                       "rawModeData": "<neighbor xmlns=\"urn:opendaylight:params:xml:ns:yang:bgp:openconfig-extensions\">\n    <neighbor-address>192.0.2.1</neighbor-address>\n    <timers>\n        <config>\n            <hold-time>90</hold-time>\n            <connect-retry>10</connect-retry>\n        </config>\n    </timers>\n    <transport>\n        <config>\n            <remote-port>179</remote-port>\n            <passive-mode>false</passive-mode>\n        </config>\n    </transport>\n    <config>\n        <peer-type>INTERNAL</peer-type>\n    </config>\n</neighbor>"
+               },
+               {
+                       "id": "4f5f4d00-2bd6-6f23-5f19-d15130f7cdb8",
+                       "headers": "Content-Type: application/xml\nAuthorization: Basic YWRtaW46YWRtaW4=\n",
+                       "url": "http://localhost:8183/restconf/operational/bgp-rib:bgp-rib/rib/hc-bgp-instance/loc-rib",
+                       "preRequestScript": null,
+                       "pathVariables": {},
+                       "method": "GET",
+                       "data": [],
+                       "dataMode": "raw",
+                       "tests": null,
+                       "currentHelper": "normal",
+                       "helperAttributes": {},
+                       "time": 1495009616064,
+                       "name": "show speeker's Loc-RIB",
+                       "description": "",
+                       "collectionId": "866862e9-5b18-cf86-e8a3-c121422e38e6",
+                       "responses": [],
+                       "rawModeData": "<neighbor xmlns=\"urn:opendaylight:params:xml:ns:yang:bgp:openconfig-extensions\">\n    <neighbor-address>10.25.1.9</neighbor-address>\n    <config>\n        <peer-group>application-peers</peer-group>\n    </config>\n</neighbor>"
+               },
+               {
+                       "id": "62045d77-ce19-7e74-468b-74843c20b5ce",
+                       "headers": "Content-Type: application/xml\nAuthorization: Basic YWRtaW46YWRtaW4=\n",
+                       "url": "http://localhost:8183/restconf/operational/bgp-rib:bgp-rib/rib/hc-bgp-instance/peer/bgp:%2F%2F10.25.1.9",
+                       "preRequestScript": null,
+                       "pathVariables": {},
+                       "method": "GET",
+                       "data": [],
+                       "dataMode": "raw",
+                       "version": 2,
+                       "tests": null,
+                       "currentHelper": "normal",
+                       "helperAttributes": {},
+                       "time": 1495116619841,
+                       "name": "show 127.0.0.3 app peer state",
+                       "description": "",
+                       "collectionId": "866862e9-5b18-cf86-e8a3-c121422e38e6",
+                       "responses": [],
+                       "rawModeData": "<neighbor xmlns=\"urn:opendaylight:params:xml:ns:yang:bgp:openconfig-extensions\">\n    <neighbor-address>192.0.2.1</neighbor-address>\n    <timers>\n        <config>\n            <hold-time>90</hold-time>\n            <connect-retry>10</connect-retry>\n        </config>\n    </timers>\n    <transport>\n        <config>\n            <remote-port>179</remote-port>\n            <passive-mode>false</passive-mode>\n        </config>\n    </transport>\n    <config>\n        <peer-type>INTERNAL</peer-type>\n    </config>\n</neighbor>"
+               },
+               {
+                       "id": "830e5608-4b7e-fbcf-e1b6-057af48570a7",
+                       "headers": "Content-Type: application/xml\nAuthorization: Basic YWRtaW46YWRtaW4=\n",
+                       "url": "http://localhost:8183/restconf/operational/bgp-rib:bgp-rib/rib/hc-bgp-instance/peer/bgp:%2F%2F127.0.0.2",
+                       "preRequestScript": null,
+                       "pathVariables": {},
+                       "method": "GET",
+                       "data": [],
+                       "dataMode": "raw",
+                       "version": 2,
+                       "tests": null,
+                       "currentHelper": "normal",
+                       "helperAttributes": {},
+                       "time": 1495009591745,
+                       "name": "show 127.0.0.2 peer state",
+                       "description": "",
+                       "collectionId": "866862e9-5b18-cf86-e8a3-c121422e38e6",
+                       "responses": [],
+                       "rawModeData": "<neighbor xmlns=\"urn:opendaylight:params:xml:ns:yang:bgp:openconfig-extensions\">\n    <neighbor-address>192.0.2.1</neighbor-address>\n    <timers>\n        <config>\n            <hold-time>90</hold-time>\n            <connect-retry>10</connect-retry>\n        </config>\n    </timers>\n    <transport>\n        <config>\n            <remote-port>179</remote-port>\n            <passive-mode>false</passive-mode>\n        </config>\n    </transport>\n    <config>\n        <peer-type>INTERNAL</peer-type>\n    </config>\n</neighbor>"
+               },
+               {
+                       "id": "aed706f7-b83c-93b2-552b-831c1576a0c6",
+                       "headers": "Content-Type: application/xml\nAuthorization: Basic YWRtaW46YWRtaW4=\n",
+                       "url": "http://localhost:8183/restconf/operational/bgp-rib:bgp-rib/rib/hc-bgp-instance",
+                       "preRequestScript": null,
+                       "pathVariables": {},
+                       "method": "GET",
+                       "data": [],
+                       "dataMode": "raw",
+                       "tests": null,
+                       "currentHelper": "normal",
+                       "helperAttributes": {},
+                       "time": 1495116911838,
+                       "name": "show hc-bgp-instance",
+                       "description": "",
+                       "collectionId": "866862e9-5b18-cf86-e8a3-c121422e38e6",
+                       "responses": [],
+                       "rawModeData": "<neighbor xmlns=\"urn:opendaylight:params:xml:ns:yang:bgp:openconfig-extensions\">\n    <neighbor-address>192.0.2.1</neighbor-address>\n    <timers>\n        <config>\n            <hold-time>90</hold-time>\n            <connect-retry>10</connect-retry>\n        </config>\n    </timers>\n    <transport>\n        <config>\n            <remote-port>179</remote-port>\n            <passive-mode>false</passive-mode>\n        </config>\n    </transport>\n    <config>\n        <peer-type>INTERNAL</peer-type>\n    </config>\n</neighbor>"
+               }
+       ]
+}
\ No newline at end of file
index 13c2d19..ccab10a 100644 (file)
             <groupId>org.opendaylight.controller</groupId>
             <artifactId>sal-core-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal-core-spi</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>junit</groupId>
index ab921a9..342bcf6 100644 (file)
             io.fd.honeycomb.infra.distro.netconf.NetconfModule,
             io.fd.honeycomb.infra.distro.netconf.NetconfReadersModule,
             io.fd.honeycomb.infra.distro.restconf.RestconfModule,
+            io.fd.honeycomb.infra.distro.bgp.BgpModule,
+            io.fd.honeycomb.infra.distro.bgp.BgpExtensionsModule,
+            io.fd.honeycomb.infra.distro.bgp.BgpReadersModule,
+            io.fd.honeycomb.infra.distro.bgp.BgpWritersModule,
             io.fd.honeycomb.infra.distro.cfgattrs.CfgAttrsModule
         </distribution.modules>
     </properties>
             <groupId>org.opendaylight.netconf</groupId>
             <artifactId>ietf-netconf-monitoring-extension</artifactId>
         </dependency>
+        <!-- ODL-BGP -->
+        <dependency>
+            <groupId>org.opendaylight.bgpcep</groupId>
+            <artifactId>bgp-rib-impl</artifactId>
+            <!-- TODO remove exclusion after bumping to Boron-SR4 -->
+            <exclusions>
+                <exclusion>
+                    <groupId>org.powermock</groupId>
+                    <artifactId>powermock</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.bgpcep</groupId>
+            <artifactId>bgp-openconfig-impl</artifactId>
+        </dependency>
+        <!-- BGP extensions -->
+        <dependency>
+            <groupId>org.opendaylight.bgpcep</groupId>
+            <artifactId>bgp-evpn</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.bgpcep</groupId>
+            <artifactId>bgp-inet</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.bgpcep</groupId>
+            <artifactId>bgp-labeled-unicast</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.bgpcep</groupId>
+            <artifactId>bgp-linkstate</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.bgpcep</groupId>
+            <artifactId>bgp-l3vpn</artifactId>
+        </dependency>
 
         <!-- Jersey + Jetty for RESTCONF -->
         <dependency>
index 4769b57..fc8fb0e 100644 (file)
@@ -31,6 +31,7 @@ import com.google.inject.ProvisionException;
 import com.google.inject.name.Names;
 import io.fd.honeycomb.data.init.DataTreeInitializer;
 import io.fd.honeycomb.data.init.InitializerRegistry;
+import io.fd.honeycomb.infra.distro.bgp.BgpServerProvider;
 import io.fd.honeycomb.infra.distro.cfgattrs.HoneycombConfiguration;
 import io.fd.honeycomb.infra.distro.initializer.InitializerPipelineModule;
 import io.fd.honeycomb.infra.distro.netconf.HoneycombNotification2NetconfProvider;
@@ -119,6 +120,11 @@ public final class Main {
                 injector.getInstance(HoneycombNotification2NetconfProvider.HoneycombNotification2Netconf.class);
             }
 
+            if (cfgAttributes.isBgpEnabled()) {
+                LOG.info("Starting BGP");
+                injector.getInstance(BgpServerProvider.BgpServer.class);
+            }
+
             try {
                 LOG.info("Initializing configuration");
                 injector.getInstance(Key.get(InitializerRegistry.class,
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BGPDispatcherImplProvider.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BGPDispatcherImplProvider.java
new file mode 100644 (file)
index 0000000..1de26c4
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import com.google.inject.Inject;
+import io.fd.honeycomb.infra.distro.ProviderTrait;
+import io.netty.channel.EventLoopGroup;
+import org.opendaylight.protocol.bgp.parser.spi.BGPExtensionConsumerContext;
+import org.opendaylight.protocol.bgp.rib.impl.BGPDispatcherImpl;
+import org.opendaylight.protocol.bgp.rib.impl.spi.BGPDispatcher;
+
+final class BGPDispatcherImplProvider extends ProviderTrait<BGPDispatcher> {
+    @Inject
+    private BGPExtensionConsumerContext consumerContext;
+    @Inject
+    private EventLoopGroup threadGroup;
+
+    @Override
+    protected BGPDispatcher create() {
+        return new BGPDispatcherImpl(consumerContext.getMessageRegistry(), threadGroup, threadGroup);
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BGPExtensionConsumerContextProvider.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BGPExtensionConsumerContextProvider.java
new file mode 100644 (file)
index 0000000..c57440d
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import com.google.inject.Inject;
+import io.fd.honeycomb.infra.distro.ProviderTrait;
+import java.util.ArrayList;
+import java.util.Set;
+import org.opendaylight.protocol.bgp.parser.spi.BGPExtensionConsumerContext;
+import org.opendaylight.protocol.bgp.parser.spi.BGPExtensionProviderActivator;
+import org.opendaylight.protocol.bgp.parser.spi.BGPExtensionProviderContext;
+import org.opendaylight.protocol.bgp.parser.spi.pojo.SimpleBGPExtensionProviderContext;
+import org.opendaylight.protocol.bgp.parser.spi.pojo.SimpleBGPExtensionProviderContextActivator;
+
+final class BGPExtensionConsumerContextProvider extends ProviderTrait<BGPExtensionConsumerContext> {
+    @Inject
+    private Set<BGPExtensionProviderActivator> activators;
+
+    @Override
+    protected BGPExtensionConsumerContext create() {
+        final BGPExtensionProviderContext ctx = new SimpleBGPExtensionProviderContext();
+        final SimpleBGPExtensionProviderContextActivator activator =
+            new SimpleBGPExtensionProviderContextActivator(ctx, new ArrayList<>(activators));
+        activator.start();
+        return ctx;
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BGPPeerRegistryProvider.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BGPPeerRegistryProvider.java
new file mode 100644 (file)
index 0000000..611656a
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static io.fd.honeycomb.translate.util.JsonUtils.readContainerEntryJson;
+import static org.opendaylight.protocol.bgp.openconfig.impl.util.OpenConfigUtil.APPLICATION_PEER_GROUP_NAME;
+import static org.opendaylight.yangtools.sal.binding.generator.impl.BindingSchemaContextUtils.findDataNodeContainer;
+
+import com.google.common.base.Optional;
+import com.google.inject.Inject;
+import io.fd.honeycomb.infra.distro.ProviderTrait;
+import java.io.InputStream;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.binding.impl.BindingToNormalizedNodeCodec;
+import org.opendaylight.controller.sal.core.api.model.SchemaService;
+import org.opendaylight.protocol.bgp.openconfig.spi.BGPOpenConfigMappingService;
+import org.opendaylight.protocol.bgp.rib.impl.StrictBGPPeerRegistry;
+import org.opendaylight.protocol.bgp.rib.impl.config.AppPeer;
+import org.opendaylight.protocol.bgp.rib.impl.config.BgpPeer;
+import org.opendaylight.protocol.bgp.rib.impl.spi.BGPPeerRegistry;
+import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.BgpNeighborPeerGroupConfig;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.neighbors.Neighbor;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.top.Bgp;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.top.bgp.Neighbors;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.NetworkInstances;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.NetworkInstance;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.NetworkInstanceKey;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.network.instance.Protocols;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.network.instance.protocols.Protocol;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.network.instance.protocols.ProtocolKey;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.policy.types.rev151009.BGP;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.openconfig.extensions.rev160614.Config2;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.openconfig.extensions.rev160614.Protocol1;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+final class BGPPeerRegistryProvider extends ProviderTrait<BGPPeerRegistry> {
+    private static final String PEERS_CFG = "/bgp-peers.json";
+    @Inject
+    private BindingToNormalizedNodeCodec codec;
+    @Inject
+    private RIB globalRib;
+    @Inject
+    private BGPOpenConfigMappingService mappingService;
+    @Inject
+    private SchemaService schemaService;
+
+    @Override
+    protected BGPPeerRegistry create() {
+        final BGPPeerRegistry peerRegistry = StrictBGPPeerRegistry.instance();
+        final Neighbors neighbors = readNeighbours();
+        for (final Neighbor neighbor : neighbors.getNeighbor()) {
+            if (isApplicationPeer(neighbor)) {
+                new AppPeer().start(globalRib, neighbor, mappingService, null);
+            } else {
+                new BgpPeer(null, peerRegistry).start(globalRib, neighbor, mappingService, null);
+            }
+        }
+        return peerRegistry;
+    }
+
+    private Neighbors readNeighbours() {
+        final InputStream resourceStream = this.getClass().getResourceAsStream(PEERS_CFG);
+        checkState(resourceStream != null, "Resource %s not found", PEERS_CFG);
+
+        final InstanceIdentifier<Bgp> bgpII = InstanceIdentifier.create(NetworkInstances.class)
+            .child(NetworkInstance.class, new NetworkInstanceKey("dummy-value")).child(Protocols.class)
+            .child(Protocol.class, new ProtocolKey(BGP.class, "dummy-value")).augmentation(Protocol1.class)
+            .child(Bgp.class);
+        final InstanceIdentifier<Neighbors> neighborsII = bgpII.child(Neighbors.class);
+
+        final YangInstanceIdentifier neighborsYII = codec.toYangInstanceIdentifier(neighborsII);
+        final SchemaContext schemaContext = schemaService.getGlobalContext();
+        final Optional<DataNodeContainer> parentNode = findDataNodeContainer(schemaContext, bgpII);
+        final ContainerNode parentContainer = readContainerEntryJson(schemaContext, resourceStream,
+            (SchemaNode) parentNode.get(),
+            (YangInstanceIdentifier.NodeIdentifier) neighborsYII.getLastPathArgument());
+        final NormalizedNode<?, ?> neighborsContainer = parentContainer.getValue().iterator().next();
+
+        final Map.Entry<InstanceIdentifier<?>, DataObject> entry = codec.fromNormalizedNode(neighborsYII, neighborsContainer);
+        checkNotNull(entry, "Failed to deserialize neighbours configuration at %s", PEERS_CFG);
+        return (Neighbors) entry.getValue();
+    }
+
+    private static boolean isApplicationPeer(@Nonnull final Neighbor neighbor) {
+        return java.util.Optional.of(neighbor.getConfig())
+            .map(config -> config.getAugmentation(Config2.class))
+            .map(BgpNeighborPeerGroupConfig::getPeerGroup)
+            .map(APPLICATION_PEER_GROUP_NAME::equals)
+            .orElse(false);
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpExtensionsModule.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpExtensionsModule.java
new file mode 100644 (file)
index 0000000..1940224
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.multibindings.Multibinder;
+import org.opendaylight.protocol.bgp.parser.spi.BGPExtensionConsumerContext;
+import org.opendaylight.protocol.bgp.parser.spi.BGPExtensionProviderActivator;
+import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
+import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionProviderActivator;
+
+/**
+ * Registers BGP extensions provided by ODL implementation.
+ * TODO add support for flowspec (requires some special initialization)
+ */
+public final class BgpExtensionsModule extends AbstractModule {
+
+    protected void configure() {
+        // This should be part of BgpModule, but that one is Private and Multibinders + private BASE_MODULES
+        // do not work together, that's why there's a dedicated module here
+        // https://github.com/google/guice/issues/906
+        configureRIBExtensions();
+        configureBGPExtensions();
+    }
+
+    private void configureRIBExtensions() {
+        final Multibinder<RIBExtensionProviderActivator> ribExtensionBinder = Multibinder.newSetBinder(binder(),
+            RIBExtensionProviderActivator.class);
+        ribExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.evpn.impl.RIBActivator.class);
+        ribExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.inet.RIBActivator.class);
+        ribExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.labeled.unicast.RIBActivator.class);
+        ribExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.linkstate.impl.RIBActivator.class);
+        ribExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.l3vpn.ipv4.RibIpv4Activator.class);
+        ribExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.l3vpn.ipv6.RibIpv6Activator.class);
+        bind(RIBExtensionConsumerContext.class).toProvider(RIBExtensionConsumerContextProvider.class);
+    }
+
+    private void configureBGPExtensions() {
+        final Multibinder<BGPExtensionProviderActivator> bgpExtensionBinder = Multibinder.newSetBinder(binder(),
+            BGPExtensionProviderActivator.class);
+        bgpExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.parser.impl.BGPActivator.class);
+        bgpExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.evpn.impl.BGPActivator.class);
+        bgpExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.inet.BGPActivator.class);
+        bgpExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.labeled.unicast.BGPActivator.class);
+        bgpExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.linkstate.impl.BGPActivator.class);
+        bgpExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.l3vpn.ipv4.BgpIpv4Activator.class);
+        bgpExtensionBinder.addBinding().to(org.opendaylight.protocol.bgp.l3vpn.ipv6.BgpIpv6Activator.class);
+        bind(BGPExtensionConsumerContext.class).toProvider(BGPExtensionConsumerContextProvider.class);
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpModule.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpModule.java
new file mode 100644 (file)
index 0000000..ca6b065
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import static io.fd.honeycomb.infra.distro.data.InmemoryDOMDataBrokerProvider.CONFIG;
+import static io.fd.honeycomb.infra.distro.data.InmemoryDOMDataBrokerProvider.OPERATIONAL;
+
+import com.google.inject.PrivateModule;
+import com.google.inject.Singleton;
+import com.google.inject.name.Names;
+import io.fd.honeycomb.infra.distro.data.BindingDataBrokerProvider;
+import io.fd.honeycomb.infra.distro.data.DataStoreProvider;
+import io.fd.honeycomb.infra.distro.data.InmemoryDOMDataBrokerProvider;
+import io.netty.channel.EventLoopGroup;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore;
+import org.opendaylight.protocol.bgp.openconfig.impl.BGPOpenConfigMappingServiceImpl;
+import org.opendaylight.protocol.bgp.openconfig.spi.BGPOpenConfigMappingService;
+import org.opendaylight.protocol.bgp.rib.impl.spi.BGPDispatcher;
+import org.opendaylight.protocol.bgp.rib.impl.spi.BGPPeerRegistry;
+import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
+
+public final class BgpModule extends PrivateModule {
+
+    static final String HONEYCOMB_BGP = "honeycomb-bgp";
+
+    protected void configure() {
+        // Create BGPDispatcher BGPDispatcher for creating BGP clients
+        bind(EventLoopGroup.class).toProvider(BgpNettyThreadGroupProvider.class).in(Singleton.class);
+        bind(BGPDispatcher.class).toProvider(BGPDispatcherImplProvider.class).in(Singleton.class);
+
+        configureRIB();
+
+        // Configure peer registry
+        bind(BGPOpenConfigMappingService.class).toInstance(new BGPOpenConfigMappingServiceImpl());
+        bind(BGPPeerRegistry.class).toProvider(BGPPeerRegistryProvider.class);
+
+        // Create BGP server instance
+        bind(BgpServerProvider.BgpServer.class).toProvider(BgpServerProvider.class).in(Singleton.class);
+        expose(BgpServerProvider.BgpServer.class);
+    }
+
+    private void configureRIB() {
+        // Create inmemory config data store for HONEYCOMB_BGP
+        bind(InMemoryDOMDataStore.class).annotatedWith(Names.named(CONFIG))
+            .toProvider(new DataStoreProvider(CONFIG, LogicalDatastoreType.CONFIGURATION))
+            .in(Singleton.class);
+
+        // Create inmemory operational data store for HONEYCOMB_BGP
+        bind(InMemoryDOMDataStore.class).annotatedWith(Names.named(OPERATIONAL))
+            .toProvider(new DataStoreProvider(OPERATIONAL, LogicalDatastoreType.OPERATIONAL))
+            .in(Singleton.class);
+
+        // Wrap datastores as DOMDataBroker
+        // TODO make executor service configurable
+        bind(DOMDataBroker.class).toProvider(InmemoryDOMDataBrokerProvider.class).in(Singleton.class);
+
+        // Wrap DOMDataBroker as BA data broker (required by BgpReaderFactoryProvider)
+        bind(DataBroker.class).annotatedWith(Names.named(HONEYCOMB_BGP)).toProvider(BindingDataBrokerProvider.class)
+            .in(Singleton.class);
+        expose(DataBroker.class).annotatedWith(Names.named(HONEYCOMB_BGP));
+
+        // Create RIB instance
+        bind(RIB.class).toProvider(BgpRIBProvider.class).in(Singleton.class);
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpNettyThreadGroupProvider.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpNettyThreadGroupProvider.java
new file mode 100644 (file)
index 0000000..b4ac322
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.inject.Inject;
+import io.fd.honeycomb.infra.distro.ProviderTrait;
+import io.fd.honeycomb.infra.distro.cfgattrs.HoneycombConfiguration;
+import io.netty.channel.nio.NioEventLoopGroup;
+
+final class BgpNettyThreadGroupProvider extends ProviderTrait<NioEventLoopGroup> {
+
+    @Inject
+    private HoneycombConfiguration cfgAttributes;
+
+    @Override
+    protected NioEventLoopGroup create() {
+        return new NioEventLoopGroup(cfgAttributes.bgpNettyThreads,
+                new ThreadFactoryBuilder().setNameFormat("bgp-netty-%d").build());
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpRIBProvider.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpRIBProvider.java
new file mode 100644 (file)
index 0000000..f161e95
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Inject;
+import io.fd.honeycomb.infra.distro.ProviderTrait;
+import io.fd.honeycomb.infra.distro.cfgattrs.HoneycombConfiguration;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.opendaylight.controller.md.sal.binding.impl.BindingToNormalizedNodeCodec;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.broker.impl.PingPongDataBroker;
+import org.opendaylight.controller.sal.core.api.model.SchemaService;
+import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService;
+import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
+import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration;
+import org.opendaylight.protocol.bgp.mode.api.PathSelectionMode;
+import org.opendaylight.protocol.bgp.openconfig.spi.BGPOpenConfigMappingService;
+import org.opendaylight.protocol.bgp.rib.impl.RIBImpl;
+import org.opendaylight.protocol.bgp.rib.impl.spi.BGPDispatcher;
+import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
+import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.multiprotocol.rev151009.bgp.common.afi.safi.list.AfiSafi;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.multiprotocol.rev151009.bgp.common.afi.safi.list.AfiSafiBuilder;
+import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.types.rev151009.IPV4UNICAST;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.openconfig.extensions.rev160614.AfiSafi2;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.openconfig.extensions.rev160614.AfiSafi2Builder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.RibId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.BgpId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.ClusterIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class BgpRIBProvider extends ProviderTrait<RIB> {
+
+    private static final String HC_BGP_INSTANCE_NAME = "hc-bgp-instance";
+
+    @Inject
+    private HoneycombConfiguration cfg;
+    @Inject
+    private RIBExtensionConsumerContext extensions;
+    @Inject
+    private BGPDispatcher dispatcher;
+    @Inject
+    private BindingToNormalizedNodeCodec codec;
+    @Inject
+    private DOMDataBroker domBroker;
+    @Inject
+    private BGPOpenConfigMappingService mappingService;
+    @Inject
+    private SchemaService schemaService;
+
+    @Override
+    protected RIB create() {
+        final AsNumber asNumber = new AsNumber(cfg.bgpAsNumber.get().longValue());
+        final Ipv4Address routerId = new Ipv4Address(cfg.bgpBindingAddress.get());
+        final ClusterIdentifier clusterId = new ClusterIdentifier(routerId);
+        // TODO configure other BGP Multiprotocol extensions:
+        final List<AfiSafi> afiSafi = ImmutableList.of(new AfiSafiBuilder().setAfiSafiName(IPV4UNICAST.class)
+            .addAugmentation(AfiSafi2.class,
+                new AfiSafi2Builder().setReceive(cfg.isBgpMultiplePathsEnabled())
+                    .setSendMax(cfg.bgpSendMaxMaths.get().shortValue()).build())
+            .build());
+        final Map<TablesKey, PathSelectionMode> pathSelectionModes = mappingService.toPathSelectionMode(afiSafi)
+            .entrySet().stream().collect(Collectors.toMap(entry ->
+                new TablesKey(entry.getKey().getAfi(), entry.getKey().getSafi()), Map.Entry::getValue));
+        // based on RIBImpl.createRib
+        final RIBImpl rib =
+            new RIBImpl(new NoopClusterSingletonServiceProvider(), new RibId(HC_BGP_INSTANCE_NAME), asNumber,
+            new BgpId(routerId), clusterId, extensions, dispatcher, codec, new PingPongDataBroker(domBroker),
+            mappingService.toTableTypes(afiSafi), pathSelectionModes, extensions.getClassLoadingStrategy(), null);
+
+        // required for proper RIB's CodecRegistry initialization (based on RIBImpl.start)
+        schemaService.registerSchemaContextListener(rib);
+        return rib;
+    }
+
+    /**
+     * HC does not support clustering, but BGP uses {@link ClusterSingletonServiceProvider}
+     * to initialize {@link RIBImpl}. Therefore we provide this dummy implementation.
+     */
+    private static final class NoopClusterSingletonServiceProvider implements ClusterSingletonServiceProvider {
+        private static final Logger LOG = LoggerFactory.getLogger(NoopClusterSingletonServiceProvider.class);
+
+        private static final ClusterSingletonServiceRegistration REGISTRATION =
+            () -> LOG.debug("Closing ClusterSingletonServiceRegistration");
+
+        @Override
+        public ClusterSingletonServiceRegistration registerClusterSingletonService(
+            final ClusterSingletonService clusterSingletonService) {
+            clusterSingletonService.instantiateServiceInstance();
+            return REGISTRATION;
+        }
+
+        @Override
+        public void close() throws Exception {
+            LOG.debug("Closing NoopClusterSingletonServiceProvider");
+        }
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpReaderFactoryProvider.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpReaderFactoryProvider.java
new file mode 100644 (file)
index 0000000..2e8ef8f
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import io.fd.honeycomb.infra.distro.ProviderTrait;
+import io.fd.honeycomb.translate.read.ReaderFactory;
+import io.fd.honeycomb.translate.read.registry.ModifiableReaderRegistryBuilder;
+import io.fd.honeycomb.translate.util.read.BindingBrokerReader;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.BgpRib;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.BgpRibBuilder;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+final class BgpReaderFactoryProvider extends ProviderTrait<ReaderFactory> {
+
+    @Inject
+    @Named(BgpModule.HONEYCOMB_BGP)
+    private DataBroker bgpDataBroker;
+
+    @Override
+    protected BgpReaderFactory create() {
+        return new BgpReaderFactory(bgpDataBroker);
+    }
+
+    /**
+     * {@link ReaderFactory} provides reader form BGP's dedicated data store.
+     * Makes BGP operational data available over NETCONF/RESTCONF.
+     */
+    private static final class BgpReaderFactory implements ReaderFactory {
+
+        private final DataBroker bgpDataBroker;
+
+        BgpReaderFactory(final DataBroker bgpDataBroker) {
+            this.bgpDataBroker = bgpDataBroker;
+        }
+
+        @Override
+        public void init(final ModifiableReaderRegistryBuilder registry) {
+            registry.add(new BindingBrokerReader<>(InstanceIdentifier.create(BgpRib.class),
+                bgpDataBroker, LogicalDatastoreType.OPERATIONAL, BgpRibBuilder.class));
+        }
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpReadersModule.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpReadersModule.java
new file mode 100644 (file)
index 0000000..b61cf37
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Singleton;
+import com.google.inject.multibindings.Multibinder;
+import io.fd.honeycomb.translate.read.ReaderFactory;
+
+public class BgpReadersModule extends AbstractModule {
+
+    protected void configure() {
+        // This should be part of BgpModule, but that one is Private and Multibinders + private BASE_MODULES
+        // do not work together, that's why there's a dedicated module here
+        // https://github.com/google/guice/issues/906
+        final Multibinder<ReaderFactory> binder = Multibinder.newSetBinder(binder(), ReaderFactory.class);
+        binder.addBinding().toProvider(BgpReaderFactoryProvider.class).in(Singleton.class);
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpServerProvider.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpServerProvider.java
new file mode 100644 (file)
index 0000000..4ade07f
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.Inject;
+import io.fd.honeycomb.infra.distro.ProviderTrait;
+import io.fd.honeycomb.infra.distro.cfgattrs.HoneycombConfiguration;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelConfig;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.epoll.Epoll;
+import io.netty.channel.epoll.EpollChannelOption;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import org.opendaylight.protocol.bgp.rib.impl.spi.BGPDispatcher;
+import org.opendaylight.protocol.bgp.rib.impl.spi.BGPPeerRegistry;
+import org.opendaylight.protocol.bgp.rib.impl.spi.BGPSessionPreferences;
+import org.opendaylight.protocol.bgp.rib.impl.spi.PeerRegistryListener;
+import org.opendaylight.protocol.concepts.KeyMapping;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
+
+public final class BgpServerProvider  extends ProviderTrait<BgpServerProvider.BgpServer> {
+    @Inject
+    private HoneycombConfiguration cfg;
+    @Inject
+    private BGPPeerRegistry peerRegistry;
+    @Inject
+    private BGPDispatcher dispatcher;
+
+    @Override
+    protected BgpServer create() {
+        // code based on org.opendaylight.controller.config.yang.bgp.rib.impl.BGPPeerAcceptorModule from Boron-SR3
+        final InetAddress bindingAddress;
+        try {
+            bindingAddress = InetAddress.getByName(cfg.bgpBindingAddress.get());
+        } catch (UnknownHostException e) {
+            throw new IllegalArgumentException("Illegal BGP binding address", e);
+        }
+        final InetSocketAddress address = new InetSocketAddress(bindingAddress, cfg.bgpPort.get());
+        final ChannelFuture localServer = dispatcher.createServer(peerRegistry, address);
+        localServer.addListener(future -> {
+            Preconditions.checkArgument(future.isSuccess(), "Unable to start bgp server on %s", address, future.cause());
+            final Channel channel = localServer.channel();
+            if (Epoll.isAvailable()) {
+                peerRegistry.registerPeerRegisterListener(new PeerRegistryListenerImpl(channel.config()));
+            }
+        });
+        return new BgpServer(localServer);
+    }
+
+    public static final class BgpServer {
+        private ChannelFuture localServer;
+
+        BgpServer(final ChannelFuture localServer) {
+            this.localServer = localServer;
+        }
+
+        public ChannelFuture getLocalServer() {
+            return localServer;
+        }
+    }
+
+    private static final class PeerRegistryListenerImpl implements PeerRegistryListener {
+        private final ChannelConfig channelConfig;
+        private final KeyMapping keys;
+
+        PeerRegistryListenerImpl(final ChannelConfig channelConfig) {
+            this.channelConfig = channelConfig;
+            this.keys = KeyMapping.getKeyMapping();
+        }
+        @Override
+        public void onPeerAdded(final IpAddress ip, final BGPSessionPreferences prefs) {
+            if (prefs.getMd5Password().isPresent()) {
+                this.keys.put(IetfInetUtil.INSTANCE.inetAddressFor(ip), prefs.getMd5Password().get());
+                this.channelConfig.setOption(EpollChannelOption.TCP_MD5SIG, this.keys);
+            }
+        }
+        @Override
+        public void onPeerRemoved(final IpAddress ip) {
+            if (this.keys.remove(IetfInetUtil.INSTANCE.inetAddressFor(ip)) != null) {
+                this.channelConfig.setOption(EpollChannelOption.TCP_MD5SIG, this.keys);
+            }
+        }
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpWriterFactoryProvider.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpWriterFactoryProvider.java
new file mode 100644 (file)
index 0000000..0441ec0
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import com.google.common.collect.Sets;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import io.fd.honeycomb.infra.distro.ProviderTrait;
+import io.fd.honeycomb.translate.util.write.BindingBrokerWriter;
+import io.fd.honeycomb.translate.write.WriterFactory;
+import io.fd.honeycomb.translate.write.registry.ModifiableWriterRegistryBuilder;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev150305.ipv4.routes.Ipv4Routes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev150305.ipv4.routes.ipv4.routes.Ipv4Route;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.Attributes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.LocalPref;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.path.attributes.attributes.Origin;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.ApplicationRib;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.Tables;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.next.hop.c.next.hop.ipv4.next.hop._case.Ipv4NextHop;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+final class BgpWriterFactoryProvider extends ProviderTrait<WriterFactory> {
+    @Inject
+    @Named(BgpModule.HONEYCOMB_BGP)
+    private DataBroker bgpDataBroker;
+    @Override
+    protected BgpWriterFactory create() {
+        return new BgpWriterFactory(bgpDataBroker);
+    }
+
+    /**
+     * {@link WriterFactory} for BGP cfg write integration with HC writer registry.
+     * Using BindingBrokerWriter to write BGP configuration data via dedicated broker that, unlike
+     * {@link io.fd.honeycomb.data.impl.DataBroker}, supports tx chains and DOMDataChangeListener registration
+     * extensively used by ODL's bgp.
+     *
+     * As a bonus BGP route configuration is persisted and available for read via RESTCONF/NETCONF.
+     */
+    private static final class BgpWriterFactory implements WriterFactory {
+        private final DataBroker dataBroker;
+
+        private static final InstanceIdentifier<ApplicationRib> AR_IID =
+            InstanceIdentifier.create(ApplicationRib.class);
+        private static final InstanceIdentifier<Tables> TABLES_IID = AR_IID.child(Tables.class);
+        private static final InstanceIdentifier<Ipv4Routes> IPV4_ROUTES_IID = TABLES_IID.child((Class) Ipv4Routes.class);
+        private static final InstanceIdentifier<Ipv4Route> IPV4_ROUTE_IID = IPV4_ROUTES_IID.child(Ipv4Route.class);
+
+        private BgpWriterFactory(final DataBroker dataBroker) {
+            this.dataBroker = dataBroker;
+        }
+
+        // TODO
+        // BGP models are huge, we need some kind of wildcarded subtree writer, that works for whole subtree.
+        // 1) we can either move checking handledTypes to writers (getHandledTypes, isAffected, writer.getHandedTypes, ...)
+        // but then precondition check in flatWriterRegistry might be slower (we need to check if we have all writers
+        // in order to avoid unnecessary reverts).
+        //
+        // 2) alternative is to compute all child nodes during initialization (might introduce some footprint penalty).
+        @Override
+        public void init(final ModifiableWriterRegistryBuilder registry) {
+            registry.subtreeAdd(
+                Sets.newHashSet(
+                    TABLES_IID,
+                    IPV4_ROUTES_IID,
+                    IPV4_ROUTES_IID.child(Ipv4Route.class),
+                    IPV4_ROUTE_IID.child(Attributes.class),
+                    IPV4_ROUTE_IID.child(Attributes.class).child(Origin.class),
+                    IPV4_ROUTE_IID.child(Attributes.class).child(LocalPref.class),
+                    IPV4_ROUTE_IID.child(Attributes.class).child(Ipv4NextHop.class)
+                ),
+                new BindingBrokerWriter<>(InstanceIdentifier.create(ApplicationRib.class), dataBroker)
+            );
+        }
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpWritersModule.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/BgpWritersModule.java
new file mode 100644 (file)
index 0000000..f9e2e4c
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Singleton;
+import com.google.inject.multibindings.Multibinder;
+import io.fd.honeycomb.translate.write.WriterFactory;
+
+public class BgpWritersModule extends AbstractModule {
+
+    protected void configure() {
+        // This should be part of BgpModule, but that one is Private and Multibinders + private BASE_MODULES
+        // do not work together, that's why there's a dedicated module here
+        // https://github.com/google/guice/issues/906
+        final Multibinder<WriterFactory> binder = Multibinder.newSetBinder(binder(), WriterFactory.class);
+        binder.addBinding().toProvider(BgpWriterFactoryProvider.class).in(Singleton.class);
+    }
+}
diff --git a/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/RIBExtensionConsumerContextProvider.java b/infra/minimal-distribution/src/main/java/io/fd/honeycomb/infra/distro/bgp/RIBExtensionConsumerContextProvider.java
new file mode 100644 (file)
index 0000000..480caa0
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.infra.distro.bgp;
+
+import com.google.inject.Inject;
+import io.fd.honeycomb.infra.distro.ProviderTrait;
+import java.util.ArrayList;
+import java.util.Set;
+import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
+import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionProviderActivator;
+import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionProviderContext;
+import org.opendaylight.protocol.bgp.rib.spi.SimpleRIBExtensionProviderContext;
+import org.opendaylight.protocol.bgp.rib.spi.SimpleRIBExtensionProviderContextActivator;
+
+public class RIBExtensionConsumerContextProvider extends ProviderTrait<RIBExtensionConsumerContext> {
+    @Inject
+    private Set<RIBExtensionProviderActivator> activators;
+
+    @Override
+    protected RIBExtensionConsumerContext create() {
+        final RIBExtensionProviderContext ctx = new SimpleRIBExtensionProviderContext();
+        final SimpleRIBExtensionProviderContextActivator activator =
+            new SimpleRIBExtensionProviderContextActivator(ctx, new ArrayList<>(activators));
+        activator.start();
+        return ctx;
+    }
+}
index cc46f7b..c3d406c 100644 (file)
@@ -55,6 +55,10 @@ public class HoneycombConfiguration {
         return isNetconfTcpEnabled() || isNetconfSshEnabled();
     }
 
+    public boolean isBgpEnabled() {
+        return Boolean.valueOf(bgpEnabled);
+    }
+
     public boolean isConfigPersistenceEnabled() {
         return persistConfig.isPresent() && Boolean.valueOf(persistConfig.get());
     }
@@ -62,6 +66,10 @@ public class HoneycombConfiguration {
         return persistContext.isPresent() && Boolean.valueOf(persistContext.get());
     }
 
+    public boolean isBgpMultiplePathsEnabled() {
+        return Boolean.valueOf(bgpMultiplePaths.get());
+    }
+
     @InjectConfig("persist-context")
     public Optional<String> persistContext = Optional.of("true");
     @InjectConfig("persisted-context-path")
@@ -138,6 +146,20 @@ public class HoneycombConfiguration {
     public Optional<Integer> netconfSshBindingPort;
     @InjectConfig("netconf-notification-stream-name")
     public Optional<String> netconfNotificationStreamName = Optional.of("honeycomb");
+    @InjectConfig("bgp-enabled")
+    public String bgpEnabled;
+    @InjectConfig("bgp-binding-address")
+    public Optional<String> bgpBindingAddress;
+    @InjectConfig("bgp-port")
+    public Optional<Integer> bgpPort;
+    @InjectConfig("bgp-as-number")
+    public Optional<Integer> bgpAsNumber;
+    @InjectConfig("bgp-receive-multiple-paths")
+    public Optional<String> bgpMultiplePaths;
+    @InjectConfig("bgp-send-max-paths")
+    public Optional<Integer> bgpSendMaxMaths;
+    @InjectConfig("bgp-netty-threads")
+    public Integer bgpNettyThreads;
     @InjectConfig("username")
     public String username;
     @InjectConfig("password")
@@ -178,6 +200,13 @@ public class HoneycombConfiguration {
                 .add("netconfSshBindingAddress", netconfSshBindingAddress)
                 .add("netconfSshBindingPort", netconfSshBindingPort)
                 .add("netconfNotificationStreamName", netconfNotificationStreamName)
+                .add("bgpEnabled", bgpEnabled)
+                .add("bgpBindingAddress", bgpBindingAddress)
+                .add("bgpPort", bgpPort)
+                .add("bgp-as-number", bgpAsNumber)
+                .add("bgp-netty-threads", bgpNettyThreads)
+                .add("bgp-receive-multiple-paths", bgpMultiplePaths)
+                .add("bgp-send-max-paths", bgpSendMaxMaths)
                 .add("username", username)
                 .add("password", password)
                 .toString();
diff --git a/infra/minimal-distribution/src/main/resources/honeycomb-minimal-resources/config/bgp-peers.json b/infra/minimal-distribution/src/main/resources/honeycomb-minimal-resources/config/bgp-peers.json
new file mode 100644 (file)
index 0000000..6de2065
--- /dev/null
@@ -0,0 +1,48 @@
+{
+  "bgp-openconfig-extensions:neighbors": {
+    "neighbor": [
+      {
+        "neighbor-address": "10.25.1.9",
+        "config": {
+          "peer-group": "application-peers"
+        },
+        "afi-safis": {
+          "afi-safi": [
+            {
+              "afi-safi-name": "openconfig-bgp-types:IPV4-UNICAST",
+              "receive": true,
+              "send-max": 0
+            }
+          ]
+        }
+      },
+      {
+        "neighbor-address": "127.0.0.2",
+        "config": {
+          "peer-type": "INTERNAL"
+        },
+        "timers": {
+          "config": {
+            "connect-retry": 10,
+            "hold-time": 90
+          }
+        },
+        "transport": {
+          "config": {
+            "remote-port": 17900,
+            "passive-mode": false
+          }
+        },
+        "afi-safis": {
+          "afi-safi": [
+            {
+              "afi-safi-name": "openconfig-bgp-types:IPV4-UNICAST",
+              "receive": true,
+              "send-max": 0
+            }
+          ]
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file
index 1199f85..95ab712 100644 (file)
@@ -1,8 +1,8 @@
   {
-  "persist-context": "true",
+  "persist-context": "false",
   "persisted-context-path": "/var/lib/honeycomb/persist/context/data.json",
   "persisted-context-restoration-type": "Merge",
-  "persist-config": "true",
+  "persist-config": "false",
   "persisted-config-path": "/var/lib/honeycomb/persist/config/data.json",
   "persisted-config-restoration-type": "Merge",
 
   "netconf-ssh-binding-port": 2831,
   "netconf-notification-stream-name": "honeycomb",
 
+  "bgp-enabled": "true",
+  "bgp-binding-address": "127.0.0.1",
+  "bgp-port": 1790,
+  "bgp-as-number": 65000,
+  "bgp-receive-multiple-paths": "true",
+  "bgp-send-max-paths": 0,
+  "bgp-netty-threads": 2,
+
   "username": "admin",
   "password": "admin"
 }
\ No newline at end of file
index 6dd4d4f..02e801d 100644 (file)
@@ -6,10 +6,10 @@
     </encoder>
   </appender>
   <appender name="honeycomb.log" class="ch.qos.logback.core.rolling.RollingFileAppender">
-    <file>/var/log/honeycomb/honeycomb.log</file>
+    <file>honeycomb.log</file>
 
     <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-      <fileNamePattern>/var/log/honeycomb/logs/honeycomb.%d.log.zip</fileNamePattern>
+      <fileNamePattern>logs/honeycomb.%d.log.zip</fileNamePattern>
       <maxHistory>1</maxHistory>
     </rollingPolicy>
 
index 8a8ddc3..fdd265b 100644 (file)
@@ -18,6 +18,7 @@ package io.fd.honeycomb.infra.distro;
 
 import static com.google.common.collect.ImmutableSet.of;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -30,6 +31,10 @@ import com.jcraft.jsch.JSch;
 import com.jcraft.jsch.Session;
 import com.mashape.unirest.http.HttpResponse;
 import com.mashape.unirest.http.Unirest;
+import io.fd.honeycomb.infra.distro.bgp.BgpExtensionsModule;
+import io.fd.honeycomb.infra.distro.bgp.BgpModule;
+import io.fd.honeycomb.infra.distro.bgp.BgpReadersModule;
+import io.fd.honeycomb.infra.distro.bgp.BgpWritersModule;
 import io.fd.honeycomb.infra.distro.cfgattrs.CfgAttrsModule;
 import io.fd.honeycomb.infra.distro.data.ConfigAndOperationalPipelineModule;
 import io.fd.honeycomb.infra.distro.data.context.ContextPipelineModule;
@@ -41,8 +46,8 @@ import io.fd.honeycomb.infra.distro.schema.SchemaModule;
 import io.fd.honeycomb.infra.distro.schema.YangBindingProviderModule;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.InetAddress;
 import java.net.Socket;
-import java.util.List;
 import java.util.Properties;
 import java.util.Set;
 import javax.net.ssl.SSLContext;
@@ -68,7 +73,11 @@ public class BaseMinimalDistributionTest {
     private static final int NETCONF_TCP_PORT = 7778;
     private static final int NETCONF_SSH_PORT = 2832;
     private static final String NETCONF_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0";
-    private static final int NETCONF_HELLO_WAIT = 2500;
+    private static final int HELLO_WAIT = 2500;
+
+    private static final int BGP_MSG_TYPE_OFFSET = 18; // 16 (MARKER) + 2 (LENGTH);
+    private static final byte BGP_OPEN_MSG_TYPE = 1;
+    private static final int BGP_PORT = 1790;
 
     public static final Set<Module> BASE_MODULES = of(
             new YangBindingProviderModule(),
@@ -79,6 +88,10 @@ public class BaseMinimalDistributionTest {
             new NetconfModule(),
             new NetconfReadersModule(),
             new RestconfModule(),
+            new BgpModule(),
+            new BgpExtensionsModule(),
+            new BgpReadersModule(),
+            new BgpWritersModule(),
             new CfgAttrsModule());
 
     @Before
@@ -111,13 +124,15 @@ public class BaseMinimalDistributionTest {
         assertRestconfHttp();
         LOG.info("Testing RESTCONF HTTPS");
         assertRestconfHttps();
+        LOG.info("Testing BGP");
+        assertBgp();
     }
 
     private void assertNetconfTcp() throws Exception {
         try (final Socket localhost = new Socket("127.0.0.1", NETCONF_TCP_PORT);
              final InputStream inputStream = localhost.getInputStream()) {
             // Wait until hello msg is sent from server
-            Thread.sleep(NETCONF_HELLO_WAIT);
+            Thread.sleep(HELLO_WAIT);
             final String helloMessage = inputStreamToString(inputStream);
 
             LOG.info("NETCONF TCP sent hello: {}", helloMessage);
@@ -127,11 +142,14 @@ public class BaseMinimalDistributionTest {
         }
     }
 
-    private String inputStreamToString(final InputStream inputStream) throws IOException {
+    private byte[] readMessage(final InputStream inputStream) throws IOException {
         final int available = inputStream.available();
-        final byte[] helloBytes = new byte[available];
-        ByteStreams.read(inputStream, helloBytes, 0, available);
-        return new String(helloBytes, Charsets.UTF_8);
+        final byte[] msg = new byte[available];
+        ByteStreams.read(inputStream, msg, 0, available);
+        return msg;
+    }
+    private String inputStreamToString(final InputStream inputStream) throws IOException {
+        return new String(readMessage(inputStream), Charsets.UTF_8);
     }
 
     private void assertNetconfSsh() throws Exception {
@@ -151,7 +169,7 @@ public class BaseMinimalDistributionTest {
         channel.connect(20000);
 
         // Wait until hello msg is sent from server
-        Thread.sleep(NETCONF_HELLO_WAIT);
+        Thread.sleep(HELLO_WAIT);
         final String helloMessage = inputStreamToString(inputStream);
         LOG.info("NETCONF SSH sent hello: {}", helloMessage);
 
@@ -199,4 +217,19 @@ public class BaseMinimalDistributionTest {
         assertTrue(jsonNodeHttpResponse.getStatus() >= 200);
         assertTrue(jsonNodeHttpResponse.getStatus() < 400);
     }
+
+    private void assertBgp() throws Exception {
+        final InetAddress bgpHost = InetAddress.getByName("127.0.0.1");
+        final InetAddress bgpPeerAddress = InetAddress.getByName("127.0.0.2");
+        try (final Socket localhost = new Socket(bgpHost, BGP_PORT, bgpPeerAddress, 0);
+             final InputStream inputStream = localhost.getInputStream()) {
+            // wait until bgp message is sent
+            Thread.sleep(HELLO_WAIT);
+
+            final byte[] msg = readMessage(inputStream);
+            LOG.info("Received BGP message: {}", msg);
+
+            assertEquals(BGP_OPEN_MSG_TYPE, msg[BGP_MSG_TYPE_OFFSET]);
+        }
+    }
 }
\ No newline at end of file
diff --git a/infra/minimal-distribution/src/test/resources/bgp-peers.json b/infra/minimal-distribution/src/test/resources/bgp-peers.json
new file mode 100644 (file)
index 0000000..3a7e010
--- /dev/null
@@ -0,0 +1,90 @@
+{
+  "bgp-openconfig-extensions:neighbors": {
+    "neighbor": [
+      {
+        "neighbor-address": "10.25.1.9",
+        "config": {
+          "peer-group": "application-peers"
+        },
+        "afi-safis": {
+          "afi-safi": [
+            {
+              "afi-safi-name": "openconfig-bgp-types:IPV4-UNICAST",
+              "receive": true,
+              "send-max": 0
+            }
+          ]
+        }
+      },
+      {
+        "neighbor-address": "10.25.1.10",
+        "config": {
+          "peer-group": "application-peers"
+        },
+        "afi-safis": {
+          "afi-safi": [
+            {
+              "afi-safi-name": "openconfig-bgp-types:IPV4-UNICAST",
+              "receive": true,
+              "send-max": 0
+            }
+          ]
+        }
+      },
+      {
+        "neighbor-address": "127.0.0.2",
+        "config": {
+          "peer-type": "INTERNAL"
+        },
+        "timers": {
+          "config": {
+            "connect-retry": 10,
+            "hold-time": 90
+          }
+        },
+        "transport": {
+          "config": {
+            "remote-port": 17900,
+            "passive-mode": false
+          }
+        },
+        "afi-safis": {
+          "afi-safi": [
+            {
+              "afi-safi-name": "openconfig-bgp-types:IPV4-UNICAST",
+              "receive": true,
+              "send-max": 0
+            }
+          ]
+        }
+      },
+      {
+        "neighbor-address": "127.0.0.3",
+        "config": {
+          "peer-type": "EXTERNAL"
+        },
+        "timers": {
+          "config": {
+            "connect-retry": 10,
+            "hold-time": 90
+          }
+        },
+        "transport": {
+          "config": {
+            "remote-port": 17900,
+            "passive-mode": false
+          }
+        },
+        "afi-safis": {
+          "afi-safi": [
+            {
+              "afi-safi-name": "openconfig-bgp-types:IPV4-UNICAST",
+              "receive": true,
+              "send-max": 0
+            }
+          ]
+        }
+      }
+    ]
+  }
+}
\ No newline at end of file
index a0b2a63..be62774 100644 (file)
   "netconf-ssh-binding-port": 2832,
   "netconf-notification-stream-name": "honeycomb",
 
+  "bgp-enabled": "true",
+  "bgp-binding-address": "127.0.0.1",
+  "bgp-port": 1790,
+  "bgp-as-number": 65000,
+  "bgp-receive-multiple-paths": "true",
+  "bgp-send-max-paths": 0,
+  "bgp-netty-threads": 2,
+
   "username": "admin",
   "password": "admin"
 }
\ No newline at end of file
diff --git a/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/write/BindingBrokerWriter.java b/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/write/BindingBrokerWriter.java
new file mode 100644 (file)
index 0000000..c9be4ae
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.translate.util.write;
+
+import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
+
+import com.google.common.util.concurrent.CheckedFuture;
+import io.fd.honeycomb.translate.write.WriteContext;
+import io.fd.honeycomb.translate.write.WriteFailedException;
+import io.fd.honeycomb.translate.write.Writer;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Simple DataBroker backed writer allowing to delegate writes to different brokers.
+ */
+public final class BindingBrokerWriter<D extends DataObject>
+    implements Writer<D> {
+
+    private final InstanceIdentifier<D> instanceIdentifier;
+    private final DataBroker dataBroker;
+
+    public BindingBrokerWriter(final InstanceIdentifier<D> instanceIdentifier, final DataBroker dataBroker) {
+        this.instanceIdentifier = instanceIdentifier;
+        this.dataBroker = dataBroker;
+    }
+
+    @Nonnull
+    @Override
+    public InstanceIdentifier<D> getManagedDataObjectType() {
+        return instanceIdentifier;
+    }
+
+    @Override
+    public void update(@Nonnull final InstanceIdentifier<? extends DataObject> id,
+                       @Nullable final DataObject dataBefore, @Nullable final DataObject dataAfter,
+                       @Nonnull final WriteContext ctx) throws WriteFailedException {
+        final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+        writeTransaction.put(CONFIGURATION, (InstanceIdentifier<DataObject>) id, dataAfter);
+        final CheckedFuture<Void, TransactionCommitFailedException> result = writeTransaction.submit();
+        try {
+            result.checkedGet();
+        } catch (TransactionCommitFailedException e) {
+            throw new WriteFailedException(id, e);
+        }
+    }
+}
diff --git a/infra/translate-utils/src/test/java/io/fd/honeycomb/translate/util/write/BindingBrokerWriterTest.java b/infra/translate-utils/src/test/java/io/fd/honeycomb/translate/util/write/BindingBrokerWriterTest.java
new file mode 100644 (file)
index 0000000..84dbb7b
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.translate.util.write;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.util.concurrent.Futures;
+import io.fd.honeycomb.translate.write.WriteContext;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class BindingBrokerWriterTest {
+
+    @Mock
+    private DataBroker broker;
+    private InstanceIdentifier<DataObject> id = InstanceIdentifier.create(DataObject.class);
+    @Mock
+    private WriteContext ctx;
+    @Mock
+    private WriteTransaction tx;
+    @Mock
+    private DataObject data;
+    private BindingBrokerWriter<DataObject> bbWriter;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(broker.newWriteOnlyTransaction()).thenReturn(tx);
+        when(tx.submit()).thenReturn(Futures.immediateCheckedFuture(null));
+        bbWriter = new BindingBrokerWriter<>(id, broker);
+    }
+
+    @Test
+    public void testWrite() throws Exception {
+        assertEquals(id, bbWriter.getManagedDataObjectType());
+
+        bbWriter.update(id, data, data, ctx);
+        verify(broker).newWriteOnlyTransaction();
+        verify(tx).put(LogicalDatastoreType.CONFIGURATION, id, data);
+        verify(tx).submit();
+    }
+
+    @Test(expected = io.fd.honeycomb.translate.write.WriteFailedException.class)
+    public void testFailedWrite() throws Exception {
+        when(tx.submit()).thenReturn(Futures.immediateFailedCheckedFuture(new TransactionCommitFailedException("failing")));
+        bbWriter.update(id, data, data, ctx);
+    }
+}
\ No newline at end of file