*.iws
.idea
xtend-gen
+classes
package org.opendaylight.controller.config.persist.api.storage;
import org.opendaylight.controller.config.persist.api.Persister;
-import org.osgi.framework.BundleContext;
/**
* Plugins for {@link org.opendaylight.controller.config.persist.api.Persister}
*/
public interface StorageAdapter extends Persister {
- void setProperties(BundleContext bundleContext);
+ void setProperties(PropertiesProvider propertiesProvider);
+
+
+ public interface PropertiesProvider {
+ /**
+ * Get property value for given key. Implementation of this interface is allowed to prefix
+ * the key with a namespace.
+ */
+ String getProperty(String key);
+
+ /**
+ * @return prefix + key as used in getProperty method.
+ */
+ String getFullKeyForReporting(String key);
+ }
}
import com.google.common.io.Files;
import org.apache.commons.lang3.StringUtils;
import org.opendaylight.controller.config.persist.api.storage.StorageAdapter;
-import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
private File storage;
@Override
- public void setProperties(BundleContext bundleContext) {
- File storage = extractStorageFileFromProperties(bundleContext);
+ public void setProperties(PropertiesProvider propertiesProvider) {
+ File storage = extractStorageFileFromProperties(propertiesProvider);
logger.debug("Using file {}", storage.getAbsolutePath());
// Create file if it does not exist
File parentFile = storage.getAbsoluteFile().getParentFile();
numberOfStoredBackups = numberOfBackups;
}
- private static File extractStorageFileFromProperties(BundleContext bundleContext) {
- String fileStorageProperty = bundleContext.getProperty(FILE_STORAGE_PROP);
- Preconditions.checkNotNull(fileStorageProperty, "Unable to find " + FILE_STORAGE_PROP
- + " in received context :" + bundleContext);
+ private static File extractStorageFileFromProperties(PropertiesProvider propertiesProvider) {
+ String fileStorageProperty = propertiesProvider.getProperty(FILE_STORAGE_PROP);
+ Preconditions.checkNotNull(fileStorageProperty, "Unable to find " + propertiesProvider.getFullKeyForReporting(FILE_STORAGE_PROP));
File result = new File(fileStorageProperty);
- String numberOfBAckupsAsString = bundleContext.getProperty(NUMBER_OF_BACKUPS);
+ String numberOfBAckupsAsString = propertiesProvider.getProperty(NUMBER_OF_BACKUPS);
if (numberOfBAckupsAsString != null) {
numberOfStoredBackups = Integer.valueOf(numberOfBAckupsAsString);
} else {
<artifactId>netconf-mapping-api</artifactId>
<version>${netconf.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>netconf-ssh</artifactId>
+ <version>${netconf.version}</version>
+ </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>config-netconf-connector</artifactId>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-model-api</artifactId>
</dependency>
-
<dependency>
<groupId>org.opendaylight.yangtools.model</groupId>
<artifactId>yang-ext</artifactId>
</dependency>
-
<dependency>
<groupId>org.opendaylight.controller.thirdparty</groupId>
<artifactId>ganymed</artifactId>
netconf.tcp.address=0.0.0.0
netconf.tcp.port=8383
-#netconf.tls.address=127.0.0.1
-#netconf.tls.port=8384
-#netconf.tls.keystore=
-#netconf.tls.keystore.password=
+netconf.ssh.address=0.0.0.0
+netconf.ssh.port=1830
netconf.config.persister.storageAdapterClass=org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter
-fileStorage=configuration/controller.config
-numberOfBackups=1
+netconf.config.persister.fileStorage=configuration/controller.config
+netconf.config.persister.numberOfBackups=1
+
yangstore.blacklist=.*controller.model.*
# Set Default start level for framework
<!-- Base log level -->
<logger name="org.opendaylight" level="INFO"/>
- <logger name="org.opendaylight.yangtools.yang.parser.util.ModuleDependencySort" level="ERROR"/>
<!-- Controller log level -->
<logger name="org.opendaylight.controller" level="INFO"/>
public GroupConsumerImpl() {
- InstanceIdentifier<? extends DataObject> path = InstanceIdentifier.builder(Groups.class).child(Group.class)
- .toInstance();
+ InstanceIdentifier<? extends DataObject> path = InstanceIdentifier.builder(Groups.class).toInstance();
groupService = FRMConsumerImpl.getProviderSession().getRpcService(SalGroupService.class);
clusterGroupContainerService = FRMConsumerImpl.getClusterContainerService();
public MeterConsumerImpl() {
- InstanceIdentifier<? extends DataObject> path = InstanceIdentifier.builder(Meters.class).child(Meter.class)
- .toInstance();
+ InstanceIdentifier<? extends DataObject> path = InstanceIdentifier.builder(Meters.class).toInstance();
meterService = FRMConsumerImpl.getProviderSession().getRpcService(SalMeterService.class);
clusterMeterContainerService = FRMConsumerImpl.getClusterContainerService();
private boolean inContainerMode; // being used by global instance only
public TableFeaturesConsumerImpl() {
- InstanceIdentifier<? extends DataObject> path = InstanceIdentifier.builder(Tables.class).child(Table.class)
- .toInstance();
+ InstanceIdentifier<? extends DataObject> path = InstanceIdentifier.builder(Tables.class).toInstance();
tableService = FRMConsumerImpl.getProviderSession().getRpcService(SalTableService.class);
if (null == tableService) {
}
Map<InstanceIdentifier<?>, TableFeatures> updates = new HashMap<>();
+ Map<InstanceIdentifier<?>, TableFeatures> createdEntries = new HashMap<>();
/**
* We create a plan which table features will be updated.
*
*/
void prepareUpdate() {
+ Set<Entry<InstanceIdentifier<?>, DataObject>> createdEntries = modification.getCreatedConfigurationData().entrySet();
Set<Entry<InstanceIdentifier<?>, DataObject>> puts = modification.getUpdatedConfigurationData().entrySet();
for (Entry<InstanceIdentifier<?>, DataObject> entry : puts) {
enum flow-mod-failed;
enum group-mod-failed;
enum port-mod-failed;
- enum table-mod-failed;
+ enum table-mod-failed;
enum queue-op-failed;
enum switch-config-failed;
enum role-request-failed;
}
leaf code {
- type uint16;
+ type uint16;
}
leaf data {
}
leaf exp-type {
- type uint16;
+ type uint16;
}
leaf experimenter-id {
- type uint32;
+ type uint32;
}
leaf data {
type string;
}
- }
+ }
}
\ No newline at end of file
rpc add-flow {
input {
uses node-flow;
- uses tr:transaction-aware;
+ uses tr:transaction-aware;
}
output {
uses tr:transaction-aware;
rpc remove-flow {
input {
uses node-flow;
- uses tr:transaction-aware;
+ uses tr:transaction-aware;
}
output {
uses tr:transaction-aware;
rpc update-flow {
input {
uses flow-update;
- uses tr:transaction-aware;
+ uses tr:transaction-aware;
}
output {
uses tr:transaction-aware;
rpc add-group {
input {
uses node-group;
- uses tr:transaction-aware;
+ uses tr:transaction-aware;
}
output {
uses tr:transaction-aware;
rpc remove-group {
input {
uses node-group;
- uses tr:transaction-aware;
+ uses tr:transaction-aware;
}
output {
uses tr:transaction-aware;
rpc update-group {
input {
uses group-update;
- uses tr:transaction-aware;
+ uses tr:transaction-aware;
}
output {
uses tr:transaction-aware;
import flow-capable-transaction {prefix tr;}
contact
- "Anilkumar Vishnoi
- Email: avishnoi@in.ibm.com";
-
+ "Anilkumar Vishnoi
+ Email: avishnoi@in.ibm.com";
+
revision "2013-11-11" {
description "Initial revision of group statistics service";
}
augment "/inv:nodes/inv:node" {
ext:augment-identifier "node-group-statistics";
container group-statistics {
- //config "false";
- uses group-types:group-statistics-reply;
+ //config "false";
+ uses group-types:group-statistics-reply;
}
}
- augment "/inv:nodes/inv:node" {
+ augment "/inv:nodes/inv:node" {
ext:augment-identifier "node-group-desc-stats";
container group-desc {
- //config "false";
- uses group-types:group-desc-stats-reply;
+ //config "false";
+ uses group-types:group-desc-stats-reply;
}
}
-
- augment "/inv:nodes/inv:node" {
+
+ augment "/inv:nodes/inv:node" {
ext:augment-identifier "node-group-features";
container group-features {
- //config "false";
- uses group-types:group-features-reply;
+ //config "false";
+ uses group-types:group-features-reply;
}
}
- // RPC calls
- rpc get-all-group-statistics {
- input {
- uses inv:node;
+ // RPC calls
+ rpc get-all-group-statistics {
+ input {
+ uses inv:node-context-ref;
}
output {
- uses group-types:group-statistics-reply;
+ uses group-types:group-statistics-reply;
uses tr:transaction-aware;
}
-
- }
-
- rpc get-group-statistics {
- input {
- uses inv:node;
+
+ }
+
+ rpc get-group-statistics {
+ input {
+ uses inv:node-context-ref;
leaf group-id{
- type group-types:group-id;
+ type group-types:group-id;
}
}
uses group-types:group-statistics-reply;
uses tr:transaction-aware;
}
-
- }
-
- rpc get-group-description {
- input {
- uses inv:node;
+
+ }
+
+ rpc get-group-description {
+ input {
+ uses inv:node-context-ref;
}
output {
uses group-types:group-desc-stats-reply;
uses tr:transaction-aware;
}
- }
-
- rpc get-group-features {
- input {
- uses inv:node;
+ }
+
+ rpc get-group-features {
+ input {
+ uses inv:node-context-ref;
}
output {
uses group-types:group-features-reply;
uses tr:transaction-aware;
}
- }
-
+ }
+
- //Notification calls
-
- notification group-statistics-updated {
- leaf moreReplies {
- type boolean;
- }
- uses inv:node;
- uses group-types:group-statistics-reply;
+ //Notification calls
+
+ notification group-statistics-updated {
+ leaf moreReplies {
+ type boolean;
+ }
+ uses inv:node;
+ uses group-types:group-statistics-reply;
uses tr:transaction-aware;
- }
-
- notification group-desc-stats-updated {
- leaf moreReplies {
- type boolean;
- }
- uses inv:node;
- uses group-types:group-desc-stats-reply;
+ }
+
+ notification group-desc-stats-updated {
+ leaf moreReplies {
+ type boolean;
+ }
+ uses inv:node;
+ uses group-types:group-desc-stats-reply;
uses tr:transaction-aware;
- }
+ }
- notification group-features-updated {
- leaf moreReplies {
- type boolean;
- }
- uses inv:node;
- uses group-types:group-features-reply;
+ notification group-features-updated {
+ leaf moreReplies {
+ type boolean;
+ }
+ uses inv:node;
+ uses group-types:group-features-reply;
uses tr:transaction-aware;
- }
+ }
}
import flow-capable-transaction {prefix tr;}
contact
- "Anilkumar Vishnoi
- Email: avishnoi@in.ibm.com";
+ "Anilkumar Vishnoi
+ Email: avishnoi@in.ibm.com";
revision "2013-11-11" {
description "Initial revision of meter statistics service";
augment "/inv:nodes/inv:node" {
ext:augment-identifier "node-meter-statistics";
container meter-statistics {
- //config "false";
- uses meter-types:meter-statistics-reply;
+ //config "false";
+ uses meter-types:meter-statistics-reply;
}
}
- augment "/inv:nodes/inv:node" {
+ augment "/inv:nodes/inv:node" {
ext:augment-identifier "node-meter-config-stats";
container meter-config-stats {
- //config "false";
- uses meter-types:meter-config-stats-reply;
+ //config "false";
+ uses meter-types:meter-config-stats-reply;
}
}
-
- augment "/inv:nodes/inv:node" {
+
+ augment "/inv:nodes/inv:node" {
ext:augment-identifier "node-meter-features";
container meter-features {
- //config "false";
- uses meter-types:meter-features-reply;
+ //config "false";
+ uses meter-types:meter-features-reply;
}
}
- // RPC calls
- rpc get-all-meter-statistics {
- input {
- uses inv:node;
+ // RPC calls
+ rpc get-all-meter-statistics {
+ input {
+ uses inv:node-context-ref;
}
output {
- uses meter-types:meter-statistics-reply;
- uses tr:transaction-aware;
+ uses meter-types:meter-statistics-reply;
+ uses tr:transaction-aware;
}
-
- }
-
- rpc get-meter-statistics {
- input {
- uses inv:node;
+
+ }
+
+ rpc get-meter-statistics {
+ input {
+ uses inv:node-context-ref;
leaf meter-id{
- type meter-types:meter-id;
+ type meter-types:meter-id;
}
}
output {
uses meter-types:meter-statistics-reply;
uses tr:transaction-aware;
}
-
- }
-
- rpc get-all-meter-config-statistics {
- input {
- uses inv:node;
+
+ }
+
+ rpc get-all-meter-config-statistics {
+ input {
+ uses inv:node-context-ref;
}
output {
- uses meter-types:meter-config-stats-reply;
+ uses meter-types:meter-config-stats-reply;
uses tr:transaction-aware;
}
- }
-
- rpc get-meter-features {
- input {
- uses inv:node;
+ }
+
+ rpc get-meter-features {
+ input {
+ uses inv:node-context-ref;
}
output {
- uses meter-types:meter-features-reply;
+ uses meter-types:meter-features-reply;
uses tr:transaction-aware;
}
- }
-
+ }
+
- //Notification calls
-
- notification meter-statistics-updated {
- leaf moreReplies {
- type boolean;
- }
-
- uses inv:node;
+ //Notification calls
+
+ notification meter-statistics-updated {
+ leaf moreReplies {
+ type boolean;
+ }
+
+ uses inv:node;
uses meter-types:meter-statistics-reply;
uses tr:transaction-aware;
- }
-
- notification meter-config-stats-updated {
- leaf moreReplies {
- type boolean;
- }
-
+ }
+
+ notification meter-config-stats-updated {
+ leaf moreReplies {
+ type boolean;
+ }
+
uses inv:node;
- uses meter-types:meter-config-stats-reply;
- uses tr:transaction-aware;
- }
+ uses meter-types:meter-config-stats-reply;
+ uses tr:transaction-aware;
+ }
- notification meter-features-updated {
- leaf moreReplies {
- type boolean;
- }
-
- uses inv:node;
+ notification meter-features-updated {
+ leaf moreReplies {
+ type boolean;
+ }
+
+ uses inv:node;
uses meter-types:meter-features-reply;
uses tr:transaction-aware;
- }
+ }
}
<configuration>
<instructions>
<Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
+ <Import-Package>*,org.opendaylight.yangtools.yang.binding.annotations</Import-Package>
</instructions>
</configuration>
</plugin>
val targetCls = createClass(iface.directProxyName, supertype) [
field(DELEGATE_FIELD, iface);
implementMethodsFrom(supertype) [
- body = '''return ($r) «DELEGATE_FIELD».«it.name»($$);'''
+ body = '''
+ {
+ if(«DELEGATE_FIELD» == null) {
+ throw new java.lang.IllegalStateException("No provider is processing supplied message");
+ }
+ return ($r) «DELEGATE_FIELD».«it.name»($$);
+ }
+ '''
]
]
return targetCls.toClass(iface.classLoader).newInstance as T
mavenBundle("org.opendaylight.bgpcep", "framework").versionAsInProject(), //
mavenBundle("org.opendaylight.bgpcep", "util").versionAsInProject(), //
mavenBundle("commons-codec", "commons-codec").versionAsInProject(),
-
+
mavenBundle(CONTROLLER, "config-api").versionAsInProject(), // //
mavenBundle(CONTROLLER, "config-manager").versionAsInProject(), // //
mavenBundle("commons-io", "commons-io").versionAsInProject(), //
mavenBundle(CONTROLLER, "logback-config").versionAsInProject(), //
mavenBundle(CONTROLLER, "config-persister-api").versionAsInProject(), //
mavenBundle(CONTROLLER, "netconf-api").versionAsInProject(), //
-
+
mavenBundle(CONTROLLER, "netconf-client").versionAsInProject(), //
mavenBundle(CONTROLLER, "netconf-util").versionAsInProject(), //
mavenBundle(CONTROLLER + ".thirdparty", "ganymed", "1.0-SNAPSHOT"), //
mavenBundle(CONTROLLER, "netconf-mapping-api").versionAsInProject(), //
mavenBundle(CONTROLLER, "config-persister-impl").versionAsInProject(), //
-
+
mavenBundle("io.netty", "netty-handler").versionAsInProject(), //
mavenBundle("io.netty", "netty-codec").versionAsInProject(), //
mavenBundle("io.netty", "netty-buffer").versionAsInProject(), //
mavenBundle("io.netty", "netty-transport").versionAsInProject(), //
mavenBundle("io.netty", "netty-common").versionAsInProject(), //
-
+
mavenBundle("org.opendaylight.controller.thirdparty", "exificient", "0.9.2-SNAPSHOT"), //
-
+
mavenBundle("org.apache.servicemix.bundles", "org.apache.servicemix.bundles.xerces", "2.11.0_1"),
mavenBundle("org.eclipse.birt.runtime.3_7_1", "org.apache.xml.resolver", "1.2.0"),
-
+
mavenBundle(CONTROLLER, "config-netconf-connector").versionAsInProject(), //
mavenBundle(CONTROLLER, "netconf-impl").versionAsInProject(), //
-
+
mavenBundle(CONTROLLER, "config-persister-file-adapter").versionAsInProject().noStart());
-
+
}
public static Option bindingAwareSalBundles() {
systemProperty("netconf.tcp.port").value("18383"), //
systemProperty("netconf.config.persister.storageAdapterClass").value(
"org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter"), //
- systemProperty("fileStorage").value(PathUtils.getBaseDir() + "/src/test/resources/controller.config"), //
- systemProperty("numberOfBackups").value("1") //
+ systemProperty("netconf.config.persister.fileStorage").value(PathUtils.getBaseDir() + "/src/test/resources/controller.config"), //
+ systemProperty("netconf.config.persister.numberOfBackups").value("1") //
//systemProperty("yangstore.blacklist").value(".*controller.model.*") //
);
* org/mockito/cglib/proxy/Factory have different Class objects
* for the type org/mockito/cglib/ proxy/Callback used in the
* signature
- *
+ *
* So we disable the bootdelegation. this property has no effect
* on the other OSGi implementation.
*/
/**
*
- * Use {@link #putOperationalData(Object, Object)} instead.
+ * @deprecated Use {@link #putOperationalData(Object, Object)} instead.
*
* @param path
* @param data
*/
+ @Deprecated
void putRuntimeData(P path, D data);
void putOperationalData(P path, D data);
void putConfigurationData(P path, D data);
/**
- * Use {@link #removeOperationalData(Object)}
+ * @deprecated Use {@link #removeOperationalData(Object)}
*
* @param path
*/
+ @Deprecated
void removeRuntimeData(P path);
void removeOperationalData(P path);
import javax.net.ssl.SSLContext;
import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.controller.netconf.client.NetconfSshClientDispatcher;
+import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler;
+import org.opendaylight.controller.netconf.util.handler.ssh.authentication.LoginPassword;
import org.opendaylight.controller.sal.connect.netconf.NetconfDevice;
import org.osgi.framework.BundleContext;
EventLoopGroup bossGroup = getBossThreadGroupDependency();
EventLoopGroup workerGroup = getWorkerThreadGroupDependency();
Optional<SSLContext> maybeContext = Optional.absent();
- NetconfClientDispatcher dispatcher = new NetconfClientDispatcher(maybeContext , bossGroup, workerGroup);
-
+ NetconfClientDispatcher dispatcher = null;
+ if(getTcpOnly()) {
+ dispatcher = new NetconfClientDispatcher(maybeContext , bossGroup, workerGroup);
+ } else {
+ AuthenticationHandler authHandler = new LoginPassword(getUsername(),getPassword());
+ dispatcher = new NetconfSshClientDispatcher(authHandler , bossGroup, workerGroup);
+ }
getDomRegistryDependency().registerProvider(device, bundleContext);
device.start(dispatcher);
leaf port {
type uint32;
}
+
+ leaf tcp-only {
+ type boolean;
+ }
+ leaf username {
+ type string;
+ }
+
+ leaf password {
+ type string;
+ }
container dom-registry {
uses config:service-ref {
refine type {
MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
public StructuredData readOperationalData(@PathParam("identifier") String identifier);
- @POST
- @Path("/operational/{identifier:.+}")
- @Produces({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML,
- MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
- public Response createOperationalData(@PathParam("identifier") String identifier, CompositeNode payload);
-
- @PUT
- @Path("/operational/{identifier:.+}")
- @Produces({Draft02.MediaTypes.DATA+JSON,Draft02.MediaTypes.DATA+XML,
- MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML})
- public Response updateOperationalData(@PathParam("identifier") String identifier, CompositeNode payload);
-
}
import java.util.List
import javax.ws.rs.core.Response
+import org.opendaylight.controller.md.sal.common.api.TransactionStatus
import org.opendaylight.controller.sal.rest.api.RestconfService
import org.opendaylight.yangtools.yang.data.api.CompositeNode
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
-import org.opendaylight.controller.md.sal.common.api.TransactionStatus
-import javax.ws.rs.WebApplicationException
class RestconfImpl implements RestconfService {
createConfigurationData(identifier,payload);
}
- override createOperationalData(String identifier, CompositeNode payload) {
- val identifierWithSchemaNode = identifier.resolveInstanceIdentifier
- val value = resolveNodeNamespaceBySchema(payload, identifierWithSchemaNode.schemaNode)
- val status = broker.commitOperationalDataPut(identifierWithSchemaNode.instanceIdentifier,value).get();
- switch status.result {
- case TransactionStatus.COMMITED: Response.status(Response.Status.OK).build
- default: Response.status(Response.Status.INTERNAL_SERVER_ERROR).build
- }
- }
-
- override updateOperationalData(String identifier, CompositeNode payload) {
- val identifierWithSchemaNode = identifier.resolveInstanceIdentifier
- val value = resolveNodeNamespaceBySchema(payload, identifierWithSchemaNode.schemaNode)
- val status = broker.commitOperationalDataPut(identifierWithSchemaNode.instanceIdentifier,value).get();
- switch status.result {
- case TransactionStatus.COMMITED: Response.status(Response.Status.NO_CONTENT).build
- default: Response.status(Response.Status.INTERNAL_SERVER_ERROR).build
- }
- }
-
private def InstanceIdWithSchemaNode resolveInstanceIdentifier(String identifier) {
val identifierWithSchemaNode = identifier.toInstanceIdentifier
if (identifierWithSchemaNode === null) {
response = target(uri).request(MEDIA_TYPE_DRAFT02).post(entity);
assertEquals(200, response.getStatus());
- uri = createUri("/operational/", "ietf-interfaces:interfaces/interface/eth0");
+ uri = createUri("/config/", "ietf-interfaces:interfaces/interface/eth0");
response = target(uri).request(MEDIA_TYPE_DRAFT02).put(entity);
assertEquals(204, response.getStatus());
response = target(uri).request(MEDIA_TYPE_DRAFT02).post(entity);
response = target(uri).request(MEDIA_TYPE_DRAFT02).post(entity);
assertEquals(500, response.getStatus());
- uri = createUri("/operational/", "ietf-interfaces:interfaces/interface/eth0");
+ uri = createUri("/config/", "ietf-interfaces:interfaces/interface/eth0");
response = target(uri).request(MEDIA_TYPE_DRAFT02).put(entity);
assertEquals(500, response.getStatus());
response = target(uri).request(MEDIA_TYPE_DRAFT02).post(entity);
import org.opendaylight.yang.gen.v1.urn.opendaylight.group.statistics.rev131111.GetGroupFeaturesOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.group.statistics.rev131111.OpendaylightGroupStatisticsService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.statistics.rev131111.GetAllMeterConfigStatisticsInputBuilder;
spLogger.debug("Statistics requester thread started with timer interval : {}",5000);
- statisticsRequesterThread.start();
+ //statisticsRequesterThread.start();
spLogger.info("Statistics Provider started.");
}
//We need to add check, so see if groups/meters are supported
//by the target node. Below check doesn't look good.
if(targetNode.getId().getValue().contains("openflow:")){
- sendAllGroupStatisticsRequest(targetNode);
+ InstanceIdentifier<Node> targetInstanceId = InstanceIdentifier.builder(Nodes.class).child(Node.class,targetNode.getKey()).toInstance();
+ NodeRef targetNodeRef = new NodeRef(targetInstanceId);
- sendAllMeterStatisticsRequest(targetNode);
+ sendAllGroupStatisticsRequest(targetNodeRef);
- sendGroupDescriptionRequest(targetNode);
+ sendAllMeterStatisticsRequest(targetNodeRef);
- sendGroupFeaturesRequest(targetNode);
+ sendGroupDescriptionRequest(targetNodeRef);
- sendMeterConfigStatisticsRequest(targetNode);
+ sendGroupFeaturesRequest(targetNodeRef);
- sendMeterFeaturesRequest(targetNode);
+ sendMeterConfigStatisticsRequest(targetNodeRef);
+
+ sendMeterFeaturesRequest(targetNodeRef);
}
}
}
- private void sendAllGroupStatisticsRequest(Node targetNode){
+ private void sendAllGroupStatisticsRequest(NodeRef targetNode){
final GetAllGroupStatisticsInputBuilder input = new GetAllGroupStatisticsInputBuilder();
- input.setId(targetNode.getId());
+ input.setNode(targetNode);
+ input.setNode(targetNode);
+ @SuppressWarnings("unused")
Future<RpcResult<GetAllGroupStatisticsOutput>> response =
groupStatsService.getAllGroupStatistics(input.build());
}
- private void sendGroupDescriptionRequest(Node targetNode){
+ private void sendGroupDescriptionRequest(NodeRef targetNode){
final GetGroupDescriptionInputBuilder input = new GetGroupDescriptionInputBuilder();
- input.setId(targetNode.getId());
-
+ input.setNode(targetNode);
+
+ @SuppressWarnings("unused")
Future<RpcResult<GetGroupDescriptionOutput>> response =
groupStatsService.getGroupDescription(input.build());
}
- private void sendGroupFeaturesRequest(Node targetNode){
+ private void sendGroupFeaturesRequest(NodeRef targetNode){
GetGroupFeaturesInputBuilder input = new GetGroupFeaturesInputBuilder();
- input.setId(targetNode.getId());
-
+ input.setNode(targetNode);
+
+ @SuppressWarnings("unused")
Future<RpcResult<GetGroupFeaturesOutput>> response =
groupStatsService.getGroupFeatures(input.build());
}
- private void sendAllMeterStatisticsRequest(Node targetNode){
+ private void sendAllMeterStatisticsRequest(NodeRef targetNode){
GetAllMeterStatisticsInputBuilder input = new GetAllMeterStatisticsInputBuilder();
- input.setId(targetNode.getId());
-
+ input.setNode(targetNode);
+
+ @SuppressWarnings("unused")
Future<RpcResult<GetAllMeterStatisticsOutput>> response =
meterStatsService.getAllMeterStatistics(input.build());
}
- private void sendMeterConfigStatisticsRequest(Node targetNode){
+ private void sendMeterConfigStatisticsRequest(NodeRef targetNode){
GetAllMeterConfigStatisticsInputBuilder input = new GetAllMeterConfigStatisticsInputBuilder();
- input.setId(targetNode.getId());
-
+ input.setNode(targetNode);
+
+ @SuppressWarnings("unused")
Future<RpcResult<GetAllMeterConfigStatisticsOutput>> response =
meterStatsService.getAllMeterConfigStatistics(input.build());
}
- private void sendMeterFeaturesRequest(Node targetNode){
+ private void sendMeterFeaturesRequest(NodeRef targetNode){
GetMeterFeaturesInputBuilder input = new GetMeterFeaturesInputBuilder();
- input.setId(targetNode.getId());
-
+ input.setNode(targetNode);
+
+ @SuppressWarnings("unused")
Future<RpcResult<GetMeterFeaturesOutput>> response =
meterStatsService.getMeterFeatures(input.build());
}
} catch (Throwable e) {
throw Exceptions.sneakyThrow(e);
}
-
}
}
registerToNetconf(maybeConfig.get().getCapabilities());
final String configSnapshot = maybeConfig.get().getConfigSnapshot();
+ logger.trace("Pushing following xml to netconf {}", configSnapshot);
try {
pushLastConfig(XmlUtil.readXmlToElement(configSnapshot));
} catch (SAXException | IOException e) {
return maybeConfigElement;
}
- private synchronized void pushLastConfig(Element persistedConfig) {
+ private synchronized void pushLastConfig(Element xmlToBePersisted) {
+ logger.info("Pushing last configuration to netconf");
StringBuilder response = new StringBuilder("editConfig response = {");
- Element configElement = persistedConfig;
- NetconfMessage message = createEditConfigMessage(configElement, "/netconfOp/editConfig.xml");
+
+ NetconfMessage message = createEditConfigMessage(xmlToBePersisted, "/netconfOp/editConfig.xml");
+
+ // sending message to netconf
NetconfMessage responseMessage = netconfClient.sendMessage(message);
XmlElement element = XmlElement.fromDomDocument(responseMessage.getDocument());
response.append("commit response = {");
response.append(XmlUtil.toString(responseMessage.getDocument()));
response.append("}");
- logger.debug("Last configuration loaded successfully");
+ logger.info("Last configuration loaded successfully");
+ logger.trace("Detailed message {}", response);
}
private void checkIsOk(XmlElement element, NetconfMessage responseMessage) {
}
}
- private NetconfMessage createEditConfigMessage(Element dataElement, String editConfigResourcename) {
- try (InputStream stream = getClass().getResourceAsStream(editConfigResourcename)) {
+ private static NetconfMessage createEditConfigMessage(Element dataElement, String editConfigResourcename) {
+ try (InputStream stream = ConfigPersisterNotificationHandler.class.getResourceAsStream(editConfigResourcename)) {
Preconditions.checkNotNull(stream, "Unable to load resource " + editConfigResourcename);
Document doc = XmlUtil.readXmlToDocument(stream);
import com.google.common.base.Optional;
import org.opendaylight.controller.config.persist.api.storage.StorageAdapter;
-import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(NoOpStorageAdapter.class);
@Override
- public void setProperties(BundleContext bundleContext) {
- logger.debug("setProperties called with {}", bundleContext);
+ public void setProperties(PropertiesProvider propertiesProvider) {
+ logger.debug("setProperties called with {}", propertiesProvider);
}
@Override
import com.google.common.base.Optional;
import org.opendaylight.controller.config.persist.api.Persister;
import org.opendaylight.controller.config.persist.api.storage.StorageAdapter;
-import org.osgi.framework.BundleContext;
+import org.opendaylight.controller.config.persist.api.storage.StorageAdapter.PropertiesProvider;
+import org.opendaylight.controller.netconf.persist.impl.osgi.ConfigPersisterActivator;
import java.io.IOException;
*/
public final class PersisterImpl implements Persister {
- public static final String STORAGE_ADAPTER_CLASS_PROP = "netconf.config.persister.storageAdapterClass";
+
private final StorageAdapter storage;
public PersisterImpl(StorageAdapter storage) {
this.storage = storage;
}
- public static Optional<PersisterImpl> createFromProperties(BundleContext bundleContext) {
- String storageAdapterClass = bundleContext.getProperty(STORAGE_ADAPTER_CLASS_PROP);
+ public static PersisterImpl createFromProperties(PropertiesProvider propertiesProvider) {
+ String storageAdapterClass = propertiesProvider.getProperty(ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX);
StorageAdapter storage;
if (storageAdapterClass == null || storageAdapterClass.equals("")) {
- return Optional.absent();
+ throw new IllegalStateException("No persister is defined in " +
+ propertiesProvider.getFullKeyForReporting(ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX)
+ + " property. For noop persister use " + NoOpStorageAdapter.class.getCanonicalName()
+ + " . Persister is not operational");
}
try {
- storage = StorageAdapter.class.cast(resolveClass(storageAdapterClass, StorageAdapter.class).newInstance());
- storage.setProperties(bundleContext);
+ Class<?> clazz = Class.forName(storageAdapterClass);
+ boolean implementsCorrectIfc = StorageAdapter.class.isAssignableFrom(clazz);
+ if (implementsCorrectIfc == false) {
+ throw new IllegalArgumentException("Storage adapter " + clazz + " does not implement " + StorageAdapter.class);
+ }
+ storage = StorageAdapter.class.cast(clazz.newInstance());
+
+ storage.setProperties(propertiesProvider);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new IllegalArgumentException("Unable to instantiate storage adapter from " + storageAdapterClass, e);
}
- return Optional.of(new PersisterImpl(storage));
- }
-
- private static Class<?> resolveClass(String storageAdapterClass, Class<?> baseType) throws ClassNotFoundException {
- Class<?> clazz = Class.forName(storageAdapterClass);
- if (!isImplemented(baseType, clazz))
- throw new IllegalArgumentException("Storage adapter " + clazz + " has to implement " + baseType);
- return clazz;
- }
-
- private static boolean isImplemented(Class<?> expectedIface, Class<?> byClazz) {
- for (Class<?> iface : byClazz.getInterfaces()) {
- if (iface.equals(expectedIface))
- return true;
- }
- return false;
+ return new PersisterImpl(storage);
}
@Override
package org.opendaylight.controller.netconf.persist.impl.osgi;
-import com.google.common.base.Optional;
+import org.opendaylight.controller.config.persist.api.storage.StorageAdapter.PropertiesProvider;
import org.opendaylight.controller.netconf.persist.impl.ConfigPersisterNotificationHandler;
-import org.opendaylight.controller.netconf.persist.impl.NoOpStorageAdapter;
import org.opendaylight.controller.netconf.persist.impl.PersisterImpl;
import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
-import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil.TLSConfiguration;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
private Thread initializationThread;
+ private static final String NETCONF_CONFIG_PERSISTER_PREFIX = "netconf.config.persister.";
+ public static final String STORAGE_ADAPTER_CLASS_PROP_SUFFIX = "storageAdapterClass";
+
@Override
- public void start(BundleContext context) throws Exception {
- logger.debug("ConfigPersister activator started");
-
- Optional<PersisterImpl> maybePersister = PersisterImpl.createFromProperties(context);
- if (maybePersister.isPresent() == false) {
- throw new IllegalStateException("No persister is defined in " + PersisterImpl.STORAGE_ADAPTER_CLASS_PROP
- + " property. For noop persister use " + NoOpStorageAdapter.class.getCanonicalName()
- + " . Persister is not operational");
- }
-
- Optional<TLSConfiguration> maybeTLSConfiguration = NetconfConfigUtil.extractTLSConfiguration(context);
- Optional<InetSocketAddress> maybeTCPAddress = NetconfConfigUtil.extractTCPNetconfAddress(context);
-
- InetSocketAddress address;
- if (maybeTLSConfiguration.isPresent()) {
- throw new UnsupportedOperationException("TLS is currently not supported for persister");
- } else if (maybeTCPAddress.isPresent()) {
- address = maybeTCPAddress.get();
- } else {
- throw new IllegalStateException("Netconf is not configured, persister is not operational");
- }
-
- PersisterImpl persister = maybePersister.get();
+ public void start(final BundleContext context) throws Exception {
+ logger.debug("ConfigPersister starting");
+
+ PropertiesProvider propertiesProvider = new PropertiesProvider() {
+ @Override
+ public String getProperty(String key) {
+ return context.getProperty(getFullKeyForReporting(key));
+ }
+
+ @Override
+ public String getFullKeyForReporting(String key) {
+ return NETCONF_CONFIG_PERSISTER_PREFIX + key;
+ }
+ };
+
+ PersisterImpl persister = PersisterImpl.createFromProperties(propertiesProvider);
+
+ InetSocketAddress address = NetconfConfigUtil.extractTCPNetconfAddress(context,
+ "Netconf is not configured, persister is not operational");
configPersisterNotificationHandler = new ConfigPersisterNotificationHandler(persister, address,
platformMBeanServer);
+
+ // offload initialization to another thread in order to stop blocking activator
Runnable initializationRunnable = new Runnable() {
@Override
public void run() {
import org.mockito.MockitoAnnotations;
import org.opendaylight.controller.config.persist.api.Persister;
import org.opendaylight.controller.config.persist.api.storage.StorageAdapter;
+import org.opendaylight.controller.config.persist.api.storage.StorageAdapter.PropertiesProvider;
import org.opendaylight.controller.config.persist.storage.file.FileStorageAdapter;
-import org.osgi.framework.BundleContext;
+import org.opendaylight.controller.netconf.persist.impl.osgi.ConfigPersisterActivator;
import java.io.File;
import java.io.IOException;
import static org.junit.Assert.fail;
import static org.junit.matchers.JUnitMatchers.containsString;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
public class PersisterImplTest {
@Mock
- BundleContext mockedContext;
+ TestingPropertiesProvider propertiesProvider;
+
+ class TestingPropertiesProvider implements PropertiesProvider {
+ @Override
+ public String getFullKeyForReporting(String key) {
+ return "prefix" + key;
+ }
+
+ @Override
+ public String getProperty(String key) {
+ throw new UnsupportedOperationException("should be mocked");
+ }
+ }
@Before
public void setUpMocks() {
MockitoAnnotations.initMocks(this);
+ doCallRealMethod().when(propertiesProvider).getFullKeyForReporting(anyString());
}
@Test
public void testFromProperties() throws Exception {
- doReturn(MockAdapter.class.getName()).when(mockedContext).getProperty(
- PersisterImpl.STORAGE_ADAPTER_CLASS_PROP);
+ doReturn(MockAdapter.class.getName()).when(propertiesProvider).getProperty(
+ ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX);
- PersisterImpl persisterImpl = PersisterImpl.createFromProperties(mockedContext).get();
+ PersisterImpl persisterImpl = PersisterImpl.createFromProperties(propertiesProvider);
persisterImpl.persistConfig(null);
persisterImpl.loadLastConfig();
persisterImpl.persistConfig(null);
assertEquals(1, MockAdapter.props);
}
+
@Test
public void testFromProperties2() throws Exception {
- mockedContext = mock(BundleContext.class);
- doReturn(FileStorageAdapter.class.getName()).when(mockedContext).getProperty(
- PersisterImpl.STORAGE_ADAPTER_CLASS_PROP);
+
+ doReturn(FileStorageAdapter.class.getName()).when(propertiesProvider).getProperty(
+ ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX);
+
doReturn("target" + File.separator + "generated-test-sources" + File.separator + "testFile").when(
- mockedContext).getProperty(FileStorageAdapter.FILE_STORAGE_PROP);
- doReturn("mockedContext").when(mockedContext).toString();
- doReturn(null).when(mockedContext).getProperty("numberOfBackups");
+ propertiesProvider).getProperty(FileStorageAdapter.FILE_STORAGE_PROP);
+ doReturn("propertiesProvider").when(propertiesProvider).toString();
+ doReturn(null).when(propertiesProvider).getProperty("numberOfBackups");
- PersisterImpl persisterImpl = PersisterImpl.createFromProperties(mockedContext).get();
+ PersisterImpl persisterImpl = PersisterImpl.createFromProperties(propertiesProvider);
assertTrue(persisterImpl.getStorage() instanceof FileStorageAdapter);
}
@Test
public void testFromProperties3() throws Exception {
- mockedContext = mock(BundleContext.class);
- doReturn(FileStorageAdapter.class.getName()).when(mockedContext).getProperty(
- PersisterImpl.STORAGE_ADAPTER_CLASS_PROP);
+
+ doReturn(FileStorageAdapter.class.getName()).when(propertiesProvider).getProperty(
+ ConfigPersisterActivator.STORAGE_ADAPTER_CLASS_PROP_SUFFIX);
doReturn("target" + File.separator + "generated-test-sources" + File.separator + "testFile").when(
- mockedContext).getProperty(FileStorageAdapter.FILE_STORAGE_PROP);
- doReturn("mockedContext").when(mockedContext).toString();
- doReturn("0").when(mockedContext).getProperty("numberOfBackups");
+ propertiesProvider).getProperty(FileStorageAdapter.FILE_STORAGE_PROP);
+ doReturn("propertiesProvider").when(propertiesProvider).toString();
+ doReturn("0").when(propertiesProvider).getProperty("numberOfBackups");
try {
- PersisterImpl.createFromProperties(mockedContext).get();
+ PersisterImpl.createFromProperties(propertiesProvider);
fail();
} catch (RuntimeException e) {
assertThat(
static int props = 0;
@Override
- public void setProperties(BundleContext configProvider) {
+ public void setProperties(PropertiesProvider propertiesProvider) {
props++;
}
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
-
-import java.io.IOException;
-import java.util.Map;
-
import org.opendaylight.protocol.framework.AbstractProtocolSession;
import org.opendaylight.protocol.framework.ProtocolMessageDecoder;
import org.opendaylight.protocol.framework.ProtocolMessageEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.util.Map;
+
public abstract class NetconfSession extends AbstractProtocolSession<NetconfMessage> {
private ChannelHandler exiEncoder;
private String exiEncoderName;
private String removeAfterMessageSentname;
private String pmeName,pmdName;
- private final Channel channel;
+ protected final Channel channel;
private final SessionListener sessionListener;
private final long sessionId;
private boolean up = false;
private static final Logger logger = LoggerFactory.getLogger(NetconfSession.class);
- private static final int T = 0;
protected NetconfSession(SessionListener sessionListener, Channel channel, long sessionId) {
this.sessionListener = sessionListener;
javax.xml.xpath,
org.opendaylight.controller.netconf.api,
org.opendaylight.controller.netconf.util,
- org.opendaylight.controller.netconf.util.xml,
+ org.opendaylight.controller.netconf.util.*,
org.opendaylight.protocol.framework,
org.slf4j,
org.w3c.dom,
- org.xml.sax
+ org.xml.sax,
+ io.netty.handler.codec
</Import-Package>
</instructions>
</configuration>
private NetconfClient(String clientLabelForLogging, InetSocketAddress address, ReconnectStrategy strat, NetconfClientDispatcher netconfClientDispatcher) throws InterruptedException {
this.label = clientLabelForLogging;
dispatch = netconfClientDispatcher;
-
sessionListener = new NetconfClientSessionListener();
Future<NetconfClientSession> clientFuture = dispatch.createClient(address, sessionListener, strat);
this.address = address;
package org.opendaylight.controller.netconf.client;
import io.netty.channel.Channel;
-
-import java.util.Collection;
-
import org.opendaylight.controller.netconf.api.NetconfSession;
import org.opendaylight.protocol.framework.SessionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Collection;
+
public class NetconfClientSession extends NetconfSession {
private static final Logger logger = LoggerFactory.getLogger(NetconfClientSession.class);
public Collection<String> getServerCapabilities() {
return capabilities;
}
-
}
package org.opendaylight.controller.netconf.client;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import javax.net.ssl.SSLContext;
+
+import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.api.NetconfSession;
+import org.opendaylight.controller.netconf.api.NetconfTerminationReason;
+import org.opendaylight.controller.netconf.util.AbstractChannelInitializer;
+import org.opendaylight.controller.netconf.util.handler.FramingMechanismHandlerFactory;
+import org.opendaylight.controller.netconf.util.handler.NetconfMessageAggregator;
+import org.opendaylight.controller.netconf.util.handler.ssh.SshHandler;
+import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler;
+import org.opendaylight.controller.netconf.util.handler.ssh.client.Invoker;
+import org.opendaylight.controller.netconf.util.messages.FramingMechanism;
+import org.opendaylight.controller.netconf.util.messages.NetconfMessageFactory;
+import org.opendaylight.protocol.framework.ProtocolHandlerFactory;
+import org.opendaylight.protocol.framework.ProtocolMessageDecoder;
+import org.opendaylight.protocol.framework.ProtocolMessageEncoder;
+import org.opendaylight.protocol.framework.ReconnectStrategy;
+import org.opendaylight.protocol.framework.SessionListener;
+import org.opendaylight.protocol.framework.SessionListenerFactory;
+
+import com.google.common.base.Optional;
+
+import io.netty.channel.ChannelHandler;
import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.util.HashedWheelTimer;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.Promise;
public class NetconfSshClientDispatcher extends NetconfClientDispatcher {
- public NetconfSshClientDispatcher(EventLoopGroup bossGroup, EventLoopGroup workerGroup) {
- super(null, bossGroup, workerGroup);
+ private AuthenticationHandler authHandler;
+ private HashedWheelTimer timer;
+ private NetconfClientSessionNegotiatorFactory negotatorFactory;
+
+ public NetconfSshClientDispatcher(AuthenticationHandler authHandler, EventLoopGroup bossGroup,
+ EventLoopGroup workerGroup) {
+ super(Optional.<SSLContext> absent(), bossGroup, workerGroup);
+ this.authHandler = authHandler;
+ this.timer = new HashedWheelTimer();
+ this.negotatorFactory = new NetconfClientSessionNegotiatorFactory(timer);
+ }
+
+ @Override
+ public Future<NetconfClientSession> createClient(InetSocketAddress address,
+ final NetconfClientSessionListener sessionListener, ReconnectStrategy strat) {
+ return super.createClient(address, strat, new PipelineInitializer<NetconfClientSession>() {
+
+ @Override
+ public void initializeChannel(SocketChannel arg0, Promise<NetconfClientSession> arg1) {
+ new NetconfSshClientInitializer(authHandler, negotatorFactory, sessionListener).initialize(arg0, arg1);
+ }
+
+ });
+ }
+
+ private static final class NetconfSshClientInitializer extends AbstractChannelInitializer {
+
+ private final NetconfHandlerFactory handlerFactory;
+ private final AuthenticationHandler authenticationHandler;
+ private final NetconfClientSessionNegotiatorFactory negotiatorFactory;
+ private final NetconfClientSessionListener sessionListener;
+
+ public NetconfSshClientInitializer(AuthenticationHandler authHandler,
+ NetconfClientSessionNegotiatorFactory negotiatorFactory,
+ final NetconfClientSessionListener sessionListener) {
+ this.handlerFactory = new NetconfHandlerFactory(new NetconfMessageFactory());
+ this.authenticationHandler = authHandler;
+ this.negotiatorFactory = negotiatorFactory;
+ this.sessionListener = sessionListener;
+ }
+
+ @Override
+ public void initialize(SocketChannel ch, Promise<? extends NetconfSession> promise) {
+ try {
+ Invoker invoker = Invoker.subsystem("netconf");
+ ch.pipeline().addFirst(new SshHandler(authenticationHandler, invoker));
+ ch.pipeline().addLast("aggregator", new NetconfMessageAggregator(FramingMechanism.EOM));
+ ch.pipeline().addLast(handlerFactory.getDecoders());
+ initializeAfterDecoder(ch, promise);
+ ch.pipeline().addLast("frameEncoder",
+ FramingMechanismHandlerFactory.createHandler(FramingMechanism.EOM));
+ ch.pipeline().addLast(handlerFactory.getEncoders());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected void initializeAfterDecoder(SocketChannel ch, Promise<? extends NetconfSession> promise) {
+ ch.pipeline().addLast("negotiator", negotiatorFactory.getSessionNegotiator(new SessionListenerFactory() {
+ @Override
+ public SessionListener<NetconfMessage, NetconfClientSession, NetconfTerminationReason> getSessionListener() {
+ return sessionListener;
+ }
+ }, ch, promise));
+
+ }
+ }
+
+ private static final class NetconfHandlerFactory extends ProtocolHandlerFactory<NetconfMessage> {
+
+ public NetconfHandlerFactory(final NetconfMessageFactory msgFactory) {
+ super(msgFactory);
+ }
+
+ @Override
+ public ChannelHandler[] getEncoders() {
+ return new ChannelHandler[] { new ProtocolMessageEncoder(this.msgFactory) };
+ }
+
+ @Override
+ public ChannelHandler[] getDecoders() {
+ return new ChannelHandler[] { new ProtocolMessageDecoder(this.msgFactory) };
+ }
}
}
import org.opendaylight.controller.netconf.impl.NetconfServerSessionNegotiatorFactory;
import org.opendaylight.controller.netconf.impl.SessionIdProvider;
import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
-import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil.TLSConfiguration;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
private static final Logger logger = LoggerFactory.getLogger(NetconfImplActivator.class);
- private Optional<InetSocketAddress> maybeTCPAddress;
- private Optional<TLSConfiguration> maybeTLSConfiguration;
-
private NetconfOperationServiceFactoryTracker factoriesTracker;
private DefaultCommitNotificationProducer commitNot;
private NetconfServerDispatcher dispatch;
@Override
public void start(final BundleContext context) throws Exception {
- maybeTCPAddress = NetconfConfigUtil.extractTCPNetconfAddress(context);
- maybeTLSConfiguration = NetconfConfigUtil.extractTLSConfiguration(context);
- if (maybeTCPAddress.isPresent() == false && maybeTLSConfiguration.isPresent() == false) {
- throw new IllegalStateException("TCP nor TLS is configured, netconf not available.");
- }
+ InetSocketAddress address = NetconfConfigUtil.extractTCPNetconfAddress(context, "TCP is not configured, netconf not available.");
+
NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl();
factoriesTracker = new NetconfOperationServiceFactoryTracker(context, factoriesListener);
factoriesTracker.open();
eventLoopGroup = new NioEventLoopGroup();
- if (maybeTCPAddress.isPresent()) {
- Optional<SSLContext> maybeSSLContext = Optional.absent();
- InetSocketAddress address = maybeTCPAddress.get();
- NetconfServerDispatcher.ServerSslChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerSslChannelInitializer(
- maybeSSLContext, serverNegotiatorFactory, listenerFactory);
- dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup, eventLoopGroup);
-
- logger.info("Starting TCP netconf server at {}", address);
- dispatch.createServer(address);
- }
- if (maybeTLSConfiguration.isPresent()) {
- Optional<SSLContext> maybeSSLContext = Optional.of(maybeTLSConfiguration.get().getSslContext());
- InetSocketAddress address = maybeTLSConfiguration.get().getAddress();
- NetconfServerDispatcher.ServerSslChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerSslChannelInitializer(
- maybeSSLContext, serverNegotiatorFactory, listenerFactory);
- dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup, eventLoopGroup);
-
- logger.info("Starting TLS netconf server at {}", address);
- dispatch.createServer(address);
- }
+ NetconfServerDispatcher.ServerSslChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerSslChannelInitializer(
+ Optional.<SSLContext>absent(), serverNegotiatorFactory, listenerFactory);
+ dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup, eventLoopGroup);
+
+ logger.info("Starting TCP netconf server at {}", address);
+ dispatch.createServer(address);
+
}
@Override
<artifactId>yang-store-api</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>yang-test</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netconf-api</artifactId>
<artifactId>config-netconf-connector</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>yang-test</artifactId>
- <scope>test</scope>
- </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>config-manager</artifactId>
<artifactId>netconf-mapping-api</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-ssh</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netconf-util</artifactId>
<goal>test</goal>
</goals>
<configuration>
- <includes>
- <include>**/org/opendaylight/controller/netconf/it/*.java</include>
- </includes>
<skip>false</skip>
+ <argLine>-Dlogback.configurationFile=${maven.test.dest}/logback.xml</argLine>
</configuration>
</execution>
</executions>
package org.opendaylight.controller.netconf.it;
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.HashedWheelTimer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.management.ManagementFactory;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import javax.management.ObjectName;
+import javax.net.ssl.SSLContext;
+import javax.xml.parsers.ParserConfigurationException;
+import junit.framework.Assert;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.opendaylight.controller.netconf.impl.mapping.ExiEncoderHandler;
import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListenerImpl;
import org.opendaylight.controller.netconf.persist.impl.ConfigPersisterNotificationHandler;
+import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
import org.opendaylight.controller.netconf.util.test.XmlFileLoader;
import org.opendaylight.controller.netconf.util.xml.ExiParameters;
import org.opendaylight.controller.netconf.util.xml.XmlElement;
import org.opendaylight.controller.netconf.util.xml.XmlUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
-
-import javax.management.ObjectName;
-import javax.net.ssl.SSLContext;
-import javax.xml.parsers.ParserConfigurationException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.management.ManagementFactory;
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
+import static java.util.Collections.emptyList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.internal.util.Checks.checkNotNull;
public class NetconfITTest extends AbstractConfigTest {
- // private static final Logger logger =
- // LoggerFactory.getLogger(NetconfITTest.class);
+ private static final Logger logger = LoggerFactory.getLogger(NetconfITTest.class);
//
private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 12023);
+ private static final InetSocketAddress sshAddress = new InetSocketAddress("127.0.0.1", 10830);
+ private static final String USERNAME = "netconf";
+ private static final String PASSWORD = "netconf";
private NetconfMessage getConfig, getConfigCandidate, editConfig,
closeSession, startExi, stopExi;
private NetconfClientDispatcher clientDispatcher;
+
@Before
public void setUp() throws Exception {
super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(getModuleFactories().toArray(
"/META-INF/yang/config-test.yang", "/META-INF/yang/config-test-impl.yang",
"/META-INF/yang/ietf-inet-types.yang");
final Collection<InputStream> yangDependencies = new ArrayList<>();
+ List<String> failedToFind = new ArrayList<>();
for (String path : paths) {
- final InputStream is = checkNotNull(NetconfITTest.class.getResourceAsStream(path), path + " not found");
- yangDependencies.add(is);
+ InputStream resourceAsStream = NetconfITTest.class.getResourceAsStream(path);
+ if (resourceAsStream == null) {
+ failedToFind.add(path);
+ } else {
+ yangDependencies.add(resourceAsStream);
+ }
}
+ assertEquals("Some yang files were not found",emptyList(), failedToFind);
return yangDependencies;
}
@Test
public void testTwoSessions() throws Exception {
- try (NetconfClient netconfClient = new NetconfClient("1", tcpAddress, 4000, clientDispatcher)) {
- try (NetconfClient netconfClient2 = new NetconfClient("2", tcpAddress, 4000, clientDispatcher)) {
+ try (NetconfClient netconfClient = new NetconfClient("1", tcpAddress, 10000, clientDispatcher)) {
+ try (NetconfClient netconfClient2 = new NetconfClient("2", tcpAddress, 10000, clientDispatcher)) {
}
}
}
return netconfClient;
}
+ private void startSSHServer() throws Exception{
+ logger.info("Creating SSH server");
+ Thread sshServerThread = new Thread(NetconfSSHServer.start(10830,tcpAddress));
+ sshServerThread.setDaemon(true);
+ sshServerThread.start();
+ logger.info("SSH server on");
+ }
+
+ @Test
+ public void sshTest() throws Exception {
+ startSSHServer();
+ logger.info("creating connection");
+ Connection conn = new Connection(sshAddress.getHostName(),sshAddress.getPort());
+ Assert.assertNotNull(conn);
+ logger.info("connection created");
+ conn.connect();
+ boolean isAuthenticated = conn.authenticateWithPassword(USERNAME,PASSWORD);
+ assertTrue(isAuthenticated);
+ logger.info("user authenticated");
+ final Session sess = conn.openSession();
+ sess.startSubSystem("netconf");
+ logger.info("user authenticated");
+ sess.getStdin().write(XmlUtil.toString(this.getConfig.getDocument()).getBytes());
+
+ new Thread(){
+ public void run(){
+ while (true){
+ byte[] bytes = new byte[1024];
+ int c = 0;
+ try {
+ c = sess.getStdout().read(bytes);
+ } catch (IOException e) {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ }
+ logger.info("got data:"+bytes);
+ if (c == 0) break;
+ }
+ }
+ }.join();
+ }
+
+
}
--- /dev/null
+<configuration scan="true">
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <logger name="org.opendaylight.controller.netconf" level="DEBUG"/>
+
+ <root level="error">
+ <appender-ref ref="STDOUT" />
+ </root>
+
+</configuration>
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>netconf-subsystem</artifactId>
+ <groupId>org.opendaylight.controller</groupId>
+ <version>0.2.3-SNAPSHOT</version>
+ <relativePath>../</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>netconf-ssh</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>bundle</packaging>
+
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller.thirdparty</groupId>
+ <artifactId>ganymed</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.controller.netconf.osgi.NetconfSSHActivator</Bundle-Activator>
+ <Export-Package>
+ org.opendaylight.controller.netconf.ssh,
+ </Export-Package>
+ <Import-Package>
+ com.google.common.base,
+ com.google.common.collect,
+ ch.ethz.ssh2,
+ ch.ethz.ssh2.signature,
+ io.netty.buffer,
+ io.netty.channel,
+ io.netty.channel.nio,
+ io.netty.channel.socket,
+ io.netty.util,
+ io.netty.util.concurrent,
+ javax.annotation,
+ java.net,
+ javax.net.ssl,
+ javax.xml.namespace,
+ javax.xml.parsers,
+ javax.xml.xpath,
+ org.apache.commons.io,
+ org.opendaylight.controller.netconf.api,
+ org.opendaylight.controller.netconf.client,
+ org.opendaylight.controller.netconf.util,
+ org.opendaylight.controller.netconf.util.osgi,
+ org.opendaylight.controller.netconf.util.xml,
+ org.opendaylight.protocol.framework,
+ org.osgi.framework,
+ org.slf4j,
+ org.w3c.dom,
+ org.xml.sax
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.netconf.osgi;
+
+import com.google.common.base.Optional;
+import java.net.InetSocketAddress;
+import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
+import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Activator for netconf SSH bundle which creates SSH bridge between netconf client and netconf server. Activator
+ * starts SSH Server in its own thread. This thread is closed when activator calls stop() method. Server opens socket
+ * and listen for client connections. Each client connection creation is handled in separate
+ * {@link org.opendaylight.controller.netconf.ssh.threads.SocketThread} thread.
+ * This thread creates two additional threads {@link org.opendaylight.controller.netconf.ssh.threads.IOThread}
+ * forwarding data from/to client.IOThread closes servers session and server connection when it gets -1 on input stream.
+ * {@link org.opendaylight.controller.netconf.ssh.threads.IOThread}'s run method waits for -1 on input stream to finish.
+ * All threads are daemons.
+ **/
+public class NetconfSSHActivator implements BundleActivator{
+
+ private NetconfSSHServer server;
+ private static final Logger logger = LoggerFactory.getLogger(NetconfSSHActivator.class);
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+
+ logger.trace("Starting netconf SSH bridge.");
+
+ Optional<InetSocketAddress> sshSocketAddressOptional = NetconfConfigUtil.extractSSHNetconfAddress(context);
+ InetSocketAddress tcpSocketAddress = NetconfConfigUtil.extractTCPNetconfAddress(context,
+ "TCP is not configured, netconf ssh bridge is not available.");
+
+ if (sshSocketAddressOptional.isPresent()){
+ server = NetconfSSHServer.start(sshSocketAddressOptional.get().getPort(),tcpSocketAddress);
+ Thread serverThread = new Thread(server,"netconf SSH server thread");
+ serverThread.setDaemon(true);
+ serverThread.start();
+ logger.trace("Netconf SSH bridge up and running.");
+ } else {
+ logger.trace("No valid connection configuration for SSH bridge found.");
+ throw new Exception("No valid connection configuration for SSH bridge found.");
+ }
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ if (server != null){
+ logger.trace("Netconf SSH bridge going down ...");
+ server.stop();
+ logger.trace("Netconf SSH bridge is down ...");
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.netconf.ssh;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.controller.netconf.ssh.threads.SocketThread;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ThreadSafe
+public class NetconfSSHServer implements Runnable {
+
+ private static boolean acceptMore = true;
+ private ServerSocket ss = null;
+ private static final Logger logger = LoggerFactory.getLogger(NetconfSSHServer.class);
+ private static final AtomicLong sesssionId = new AtomicLong();
+ private final InetSocketAddress clientAddress;
+
+ private NetconfSSHServer(int serverPort,InetSocketAddress clientAddress) throws Exception{
+
+ logger.trace("Creating SSH server socket on port {}",serverPort);
+ this.ss = new ServerSocket(serverPort);
+ if (!ss.isBound()){
+ throw new Exception("Socket can't be bound to requested port :"+serverPort);
+ }
+ logger.trace("Server socket created.");
+ this.clientAddress = clientAddress;
+
+ }
+
+
+ public static NetconfSSHServer start(int serverPort, InetSocketAddress clientAddress) throws Exception {
+ return new NetconfSSHServer(serverPort, clientAddress);
+ }
+
+ public void stop() throws Exception {
+ acceptMore = false;
+ logger.trace("Closing SSH server socket.");
+ ss.close();
+ logger.trace("SSH server socket closed.");
+ }
+
+ @Override
+ public void run() {
+ while (acceptMore) {
+ logger.trace("Starting new socket thread.");
+ try {
+ SocketThread.start(ss.accept(), clientAddress, sesssionId.incrementAndGet());
+ } catch (IOException e) {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.netconf.ssh.authentication;
+
+import ch.ethz.ssh2.signature.RSAPrivateKey;
+
+public interface KeyStoreHandler {
+ public RSAPrivateKey getPrivateKey();
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.netconf.ssh.authentication;
+
+import ch.ethz.ssh2.signature.RSAPrivateKey;
+
+import java.math.BigInteger;
+
+public class RSAKey implements KeyStoreHandler {
+
+ private static RSAPrivateKey hostkey = null;
+ private static String user = "netconf";
+ private static String password = "netconf";
+ static {
+
+ BigInteger p = new BigInteger("2967886344240998436887630478678331145236162666668503940430852241825039192450179076148979094256007292741704260675085192441025058193581327559331546948442042987131728039318861235625879376246169858586459472691398815098207618446039"); //.BigInteger.probablePrime(N / 2, rnd);
+ BigInteger q = new BigInteger("4311534819291430017572425052029278681302539382618633848168923130451247487970187151403375389974616614405320169278870943605377518341666894603659873284783174749122655429409273983428000534304828056597676444751611433784228298909767"); //BigInteger.probablePrime(N / 2, rnd);
+ BigInteger phi = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));
+
+ BigInteger n = p.multiply(q);
+ BigInteger e = new BigInteger("65537");
+ BigInteger d = e.modInverse(phi);
+
+ hostkey = new RSAPrivateKey(d, e, n);
+ }
+
+ @Override
+ public RSAPrivateKey getPrivateKey() {
+ return hostkey;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.netconf.ssh.threads;
+
+import ch.ethz.ssh2.ServerConnection;
+import ch.ethz.ssh2.ServerSession;
+import java.io.InputStream;
+import java.io.OutputStream;
+import javax.annotation.concurrent.ThreadSafe;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ThreadSafe
+public class IOThread extends Thread {
+
+ private static final Logger logger = LoggerFactory.getLogger(IOThread.class);
+
+ private InputStream inputStream;
+ private OutputStream outputStream;
+ private String id;
+ private ServerSession servSession;
+ private ServerConnection servconnection;
+
+
+ public IOThread (InputStream is, OutputStream os, String id,ServerSession ss, ServerConnection conn){
+ this.inputStream = is;
+ this.outputStream = os;
+ this.servSession = ss;
+ this.servconnection = conn;
+ super.setName(id);
+ logger.trace("IOThread {} created", super.getName());
+ }
+
+ @Override
+ public void run() {
+ logger.trace("thread {} started", super.getName());
+ try {
+ IOUtils.copy(this.inputStream, this.outputStream);
+ } catch (Exception e) {
+ logger.error("inputstream -> outputstream copy error ",e);
+ }
+ logger.trace("closing server session");
+ servSession.close();
+ servconnection.close();
+ logger.trace("thread {} is closing",super.getName());
+ }
+}
--- /dev/null
+package org.opendaylight.controller.netconf.ssh.threads;
+
+
+import ch.ethz.ssh2.AuthenticationResult;
+import ch.ethz.ssh2.PtySettings;
+import ch.ethz.ssh2.ServerAuthenticationCallback;
+import ch.ethz.ssh2.ServerConnection;
+import ch.ethz.ssh2.ServerConnectionCallback;
+import ch.ethz.ssh2.ServerSession;
+import ch.ethz.ssh2.ServerSessionCallback;
+import ch.ethz.ssh2.SimpleServerSessionCallback;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.controller.netconf.ssh.authentication.RSAKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@ThreadSafe
+public class SocketThread implements Runnable, ServerAuthenticationCallback, ServerConnectionCallback
+{
+
+ private Socket socket;
+ private static final String USER = "netconf";
+ private static final String PASSWORD = "netconf";
+ private InetSocketAddress clientAddress;
+ private static final Logger logger = LoggerFactory.getLogger(SocketThread.class);
+ private ServerConnection conn = null;
+ private long sessionId;
+
+
+ public static void start(Socket socket, InetSocketAddress clientAddress, long sessionId) throws IOException{
+ Thread netconf_ssh_socket_thread = new Thread(new SocketThread(socket,clientAddress,sessionId));
+ netconf_ssh_socket_thread.setDaemon(true);
+ netconf_ssh_socket_thread.start();
+ }
+ private SocketThread(Socket socket, InetSocketAddress clientAddress, long sessionId) throws IOException {
+
+ this.socket = socket;
+ this.clientAddress = clientAddress;
+ this.sessionId = sessionId;
+
+ }
+
+ @Override
+ public void run() {
+ conn = new ServerConnection(socket);
+ RSAKey keyStore = new RSAKey();
+ conn.setRsaHostKey(keyStore.getPrivateKey());
+ conn.setAuthenticationCallback(this);
+ conn.setServerConnectionCallback(this);
+ try {
+ conn.connect();
+ } catch (IOException e) {
+ logger.error("SocketThread error ",e);
+ }
+ }
+ public ServerSessionCallback acceptSession(final ServerSession session)
+ {
+ SimpleServerSessionCallback cb = new SimpleServerSessionCallback()
+ {
+ @Override
+ public Runnable requestSubsystem(final ServerSession ss, final String subsystem) throws IOException
+ {
+ return new Runnable(){
+ public void run()
+ {
+ if (subsystem.equals("netconf")){
+ IOThread netconf_ssh_input = null;
+ IOThread netconf_ssh_output = null;
+ try {
+ String hostName = clientAddress.getHostName();
+ int portNumber = clientAddress.getPort();
+ final Socket echoSocket = new Socket(hostName, portNumber);
+ logger.trace("echo socket created");
+
+ logger.trace("starting netconf_ssh_input thread");
+ netconf_ssh_input = new IOThread(echoSocket.getInputStream(),ss.getStdin(),"input_thread_"+sessionId,ss,conn);
+ netconf_ssh_input.setDaemon(false);
+ netconf_ssh_input.start();
+
+ logger.trace("starting netconf_ssh_output thread");
+ netconf_ssh_output = new IOThread(ss.getStdout(),echoSocket.getOutputStream(),"output_thread_"+sessionId,ss,conn);
+ netconf_ssh_output.setDaemon(false);
+ netconf_ssh_output.start();
+
+ } catch (Throwable t){
+ logger.error(t.getMessage(),t);
+
+ try {
+ if (netconf_ssh_input!=null){
+ netconf_ssh_input.join();
+ }
+ } catch (InterruptedException e) {
+ logger.error("netconf_ssh_input join error ",e);
+ }
+
+ try {
+ if (netconf_ssh_output!=null){
+ netconf_ssh_output.join();
+ }
+ } catch (InterruptedException e) {
+ logger.error("netconf_ssh_output join error ",e);
+ }
+
+ }
+ } else {
+ try {
+ ss.getStdin().write("wrong subsystem requested - closing connection".getBytes());
+ ss.close();
+ } catch (IOException e) {
+ logger.debug("excpetion while sending bad subsystem response",e);
+ }
+ }
+ }
+ };
+ }
+ @Override
+ public Runnable requestPtyReq(final ServerSession ss, final PtySettings pty) throws IOException
+ {
+ return new Runnable()
+ {
+ public void run()
+ {
+ //noop
+ }
+ };
+ }
+
+ @Override
+ public Runnable requestShell(final ServerSession ss) throws IOException
+ {
+ return new Runnable()
+ {
+ public void run()
+ {
+ //noop
+ }
+ };
+ }
+ };
+
+ return cb;
+ }
+
+ public String initAuthentication(ServerConnection sc)
+ {
+ return "";
+ }
+
+ public String[] getRemainingAuthMethods(ServerConnection sc)
+ {
+ return new String[] { ServerAuthenticationCallback.METHOD_PASSWORD };
+ }
+
+ public AuthenticationResult authenticateWithNone(ServerConnection sc, String username)
+ {
+ return AuthenticationResult.FAILURE;
+ }
+
+ public AuthenticationResult authenticateWithPassword(ServerConnection sc, String username, String password)
+ {
+ if (USER.equals(username) && PASSWORD.equals(password))
+ return AuthenticationResult.SUCCESS;
+
+ return AuthenticationResult.FAILURE;
+ }
+
+ public AuthenticationResult authenticateWithPublicKey(ServerConnection sc, String username, String algorithm,
+ byte[] publickey, byte[] signature)
+ {
+ return AuthenticationResult.FAILURE;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.netconf.ssh;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import junit.framework.Assert;
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class SSHServerTest {
+
+ private static final String USER = "netconf";
+ private static final String PASSWORD = "netconf";
+ private static final String HOST = "127.0.0.1";
+ private static final int PORT = 1830;
+ private static final InetSocketAddress tcpAddress = new InetSocketAddress("127.0.0.1", 8383);
+ private static final Logger logger = LoggerFactory.getLogger(SSHServerTest.class);
+
+// @Before
+ public void startSSHServer() throws Exception{
+ logger.info("Creating SSH server");
+ NetconfSSHServer server = NetconfSSHServer.start(PORT,tcpAddress);
+ Thread sshServerThread = new Thread(server);
+ sshServerThread.setDaemon(true);
+ sshServerThread.start();
+ logger.info("SSH server on");
+ }
+
+ @Test
+ public void connect(){
+ Connection conn = new Connection(HOST,PORT);
+ Assert.assertNotNull(conn);
+ try {
+ logger.info("connecting to SSH server");
+ conn.connect();
+ logger.info("authenticating ...");
+ boolean isAuthenticated = conn.authenticateWithPassword(USER,PASSWORD);
+ Assert.assertTrue(isAuthenticated);
+ logger.info("opening session");
+ Session sess = conn.openSession();
+ logger.info("subsystem netconf");
+ sess.startSubSystem("netconf");
+ sess.getStdin().write("<?xml version=\"1.0\" encoding=\"UTF-8\"?><hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"><capabilities><capability>urn:ietf:params:netconf:base:1.1</capability></capabilities></hello>]]>]]>".getBytes());
+ IOUtils.copy(sess.getStdout(), System.out);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
org.opendaylight.controller.netconf.util.mapping,
org.opendaylight.controller.netconf.util.messages,
org.opendaylight.controller.netconf.util.handler,
+ org.opendaylight.controller.netconf.util.handler.*,
</Export-Package>
<Import-Package>
com.google.common.base,
package org.opendaylight.controller.netconf.util.handler.ssh;
+import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
+
import java.io.IOException;
import java.net.SocketAddress;
+
import org.opendaylight.controller.netconf.util.handler.ssh.authentication.AuthenticationHandler;
import org.opendaylight.controller.netconf.util.handler.ssh.client.Invoker;
import org.opendaylight.controller.netconf.util.handler.ssh.client.SshClient;
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
- this.sshClientAdapter.write((String) msg);
+ this.sshClientAdapter.write((ByteBuf) msg);
}
@Override
import java.io.IOException;
/**
- * Class Providing username/password authentication option to {@link org.opendaylight.controller.netconf.util.handler.ssh.SshHandler}
+ * Class Providing username/password authentication option to
+ * {@link org.opendaylight.controller.netconf.util.handler.ssh.SshHandler}
*/
public class LoginPassword extends AuthenticationHandler {
private final String username;
public void authenticate(Connection connection) throws IOException {
boolean isAuthenticated = connection.authenticateWithPassword(username, password);
- if (isAuthenticated == false) throw new IOException("Authentication failed.");
+ if (isAuthenticated == false)
+ throw new IOException("Authentication failed.");
}
}
import java.util.HashMap;
import java.util.Map;
-
/**
* Wrapper class around GANYMED SSH java library.
*/
private final AuthenticationHandler authenticationHandler;
private Connection connection;
- public SshClient(VirtualSocket socket,
- AuthenticationHandler authenticationHandler) throws IOException {
+ public SshClient(VirtualSocket socket, AuthenticationHandler authenticationHandler) throws IOException {
this.socket = socket;
this.authenticationHandler = authenticationHandler;
}
public SshSession openSession() throws IOException {
- if(connection == null) connect();
+ if (connection == null)
+ connect();
- Session session = connection.openSession();
+ Session session = connection.openSession();
SshSession sshSession = new SshSession(session);
openSessions.put(openSessions.size(), sshSession);
private void connect() throws IOException {
connection = new Connection(socket);
+
connection.connect();
authenticationHandler.authenticate(connection);
}
public void closeSession(SshSession session) {
- if( session.getState() == Channel.STATE_OPEN
- || session.getState() == Channel.STATE_OPENING) {
+ if (session.getState() == Channel.STATE_OPEN || session.getState() == Channel.STATE_OPENING) {
session.session.close();
}
}
public void close() {
- for(SshSession session : openSessions.values()) closeSession(session);
+ for (SshSession session : openSessions.values())
+ closeSession(session);
openSessions.clear();
- if(connection != null) connection.close();
+ if (connection != null)
+ connection.close();
}
}
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opendaylight.controller.netconf.util.handler.ssh.virtualsocket.VirtualSocketException;
+
/**
- * Worker thread class. Handles all downstream and upstream events in SSH Netty pipeline.
+ * Worker thread class. Handles all downstream and upstream events in SSH Netty
+ * pipeline.
*/
public class SshClientAdapter implements Runnable {
private final SshClient sshClient;
private InputStream stdErr;
private OutputStream stdIn;
+ private Queue<ByteBuf> postponned = new LinkedList<>();
+
+
private ChannelHandlerContext ctx;
private ChannelPromise disconnectPromise;
private final Object lock = new Object();
- public SshClientAdapter(SshClient sshClient,
- Invoker invoker) {
+ public SshClientAdapter(SshClient sshClient, Invoker invoker) {
this.sshClient = sshClient;
this.invoker = invoker;
}
try {
session = sshClient.openSession();
invoker.invoke(session);
-
stdOut = session.getStdout();
stdErr = session.getStderr();
- synchronized(lock) {
+ synchronized (lock) {
+
stdIn = session.getStdin();
+ ByteBuf message = null;
+ while ((message = postponned.poll()) != null) {
+ writeImpl(message);
+ }
}
while (stopRequested.get() == false) {
byte[] readBuff = new byte[1024];
int c = stdOut.read(readBuff);
-
+ if (c == -1) {
+ continue;
+ }
byte[] tranBuff = new byte[c];
System.arraycopy(readBuff, 0, tranBuff, 0, c);
sshClient.close();
synchronized (lock) {
- if(disconnectPromise != null) ctx.disconnect(disconnectPromise);
+ if (disconnectPromise != null)
+ ctx.disconnect(disconnectPromise);
}
}
}
// TODO: needs rework to match netconf framer API.
- public void write(String message) throws IOException {
+ public void write(ByteBuf message) throws IOException {
synchronized (lock) {
- if (stdIn == null) throw new IllegalStateException("StdIn not available");
+ if (stdIn == null) {
+ postponned.add(message);
+ return;
+ }
+ writeImpl(message);
}
- stdIn.write(message.getBytes());
+ }
+
+ private void writeImpl(ByteBuf message) throws IOException {
+ message.getBytes(0, stdIn, message.readableBytes());
stdIn.flush();
}
}
public void start(ChannelHandlerContext ctx) {
- if(this.ctx != null) return; // context is already associated.
-
+ if (this.ctx != null)
+ return; // context is already associated.
this.ctx = ctx;
new Thread(this).start();
}
private static final String PREFIX_PROP = "netconf.";
private enum InfixProp {
- tcp, tls
+ tcp, tls, ssh
}
private static final String PORT_SUFFIX_PROP = ".port";
private static final String NETCONF_TLS_KEYSTORE_PROP = PREFIX_PROP + InfixProp.tls + ".keystore";
private static final String NETCONF_TLS_KEYSTORE_PASSWORD_PROP = NETCONF_TLS_KEYSTORE_PROP + ".password";
- public static Optional<InetSocketAddress> extractTCPNetconfAddress(BundleContext context) {
- return extractSomeNetconfAddress(context, InfixProp.tcp);
+ public static InetSocketAddress extractTCPNetconfAddress(BundleContext context, String exceptionMessageIfNotFound) {
+
+ Optional<InetSocketAddress> inetSocketAddressOptional = extractSomeNetconfAddress(context, InfixProp.tcp);
+ if (inetSocketAddressOptional.isPresent() == false) {
+ throw new IllegalStateException("Netconf tcp address not found." + exceptionMessageIfNotFound);
+ }
+ return inetSocketAddressOptional.get();
}
+ public static Optional<InetSocketAddress> extractSSHNetconfAddress(BundleContext context) {
+ return extractSomeNetconfAddress(context, InfixProp.ssh);
+ }
+
+
public static Optional<TLSConfiguration> extractTLSConfiguration(BundleContext context) {
Optional<InetSocketAddress> address = extractSomeNetconfAddress(context, InfixProp.tls);
if (address.isPresent()) {
<module>config-persister-impl</module>
<module>netconf-mapping-api</module>
<module>netconf-client</module>
+ <module>netconf-ssh</module>
<module>../../third-party/ganymed</module>
<module>../../third-party/com.siemens.ct.exi</module>
</modules>
<version>${netconf.version}</version>
<type>test-jar</type>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-ssh</artifactId>
+ <version>${netconf.version}</version>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netconf-mapping-api</artifactId>
import org.openflow.protocol.OFBarrierReply;
import org.openflow.protocol.OFBarrierRequest;
import org.openflow.protocol.OFEchoReply;
+import org.openflow.protocol.OFEchoRequest;
import org.openflow.protocol.OFError;
import org.openflow.protocol.OFFeaturesReply;
import org.openflow.protocol.OFFlowMod;
break;
case ECHO_REQUEST:
OFEchoReply echoReply = (OFEchoReply) factory.getMessage(OFType.ECHO_REPLY);
+
+ // the EchoReply must have the same payload as the request
+ byte []payload = ((OFEchoRequest)msg).getPayload();
+ echoReply.setPayload(payload);
+ echoReply.setLength( (short) (echoReply.getLength() + payload.length ));
+
// respond immediately
asyncSendNow(echoReply, msg.getXid());
<extensions>true</extensions>
<configuration>
<instructions>
- <Export-Package>ch.ethz.ssh2</Export-Package>
+ <Export-Package>ch.ethz.ssh2.*</Export-Package>
<Embed-Dependency>ganymed-ssh2;scope=compile</Embed-Dependency>
<Embed-Transitive>true</Embed-Transitive>
</instructions>