BUG-1041 cli proposal #1
[controller.git] / opendaylight / netconf / netconf-cli / src / main / java / org / opendaylight / controller / netconf / cli / Main.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.netconf.cli;
9
10 import com.google.common.base.Preconditions;
11 import java.io.IOException;
12 import java.net.InetAddress;
13 import java.net.InetSocketAddress;
14 import java.net.UnknownHostException;
15 import net.sourceforge.argparse4j.ArgumentParsers;
16 import net.sourceforge.argparse4j.inf.ArgumentGroup;
17 import net.sourceforge.argparse4j.inf.ArgumentParser;
18 import net.sourceforge.argparse4j.inf.ArgumentParserException;
19 import net.sourceforge.argparse4j.inf.Namespace;
20 import org.opendaylight.controller.netconf.cli.commands.CommandDispatcher;
21 import org.opendaylight.controller.netconf.cli.commands.local.Connect;
22 import org.opendaylight.controller.netconf.cli.io.ConsoleIO;
23 import org.opendaylight.controller.netconf.cli.io.ConsoleIOImpl;
24 import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration;
25 import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder;
26 import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
27 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
28
29 /**
30  * Parse arguments, start remote device connection and start CLI after the
31  * connection is fully up
32  */
33 public class Main {
34
35     public static void main(final String[] args) {
36         final CliArgumentParser cliArgs = new CliArgumentParser();
37         try {
38             cliArgs.parse(args);
39         } catch (final ArgumentParserException e) {
40             // Just end the cli, exception was handled by the CliArgumentParser
41             return;
42         }
43
44         final ConsoleIO consoleIO;
45         try {
46             consoleIO = new ConsoleIOImpl();
47         } catch (final IOException e) {
48             handleStartupException(e);
49             return;
50         }
51
52         final SchemaContext localSchema = CommandDispatcher.parseSchema(CommandDispatcher.LOCAL_SCHEMA_PATHS);
53         final SchemaContextRegistry schemaContextRegistry = new SchemaContextRegistry(localSchema);
54
55         final CommandDispatcher commandDispatcher = new CommandDispatcher();
56         final CommandArgHandlerRegistry argumentHandlerRegistry = new CommandArgHandlerRegistry(consoleIO,
57                 schemaContextRegistry);
58         final NetconfDeviceConnectionManager connectionManager = new NetconfDeviceConnectionManager(commandDispatcher,
59                 argumentHandlerRegistry, schemaContextRegistry, consoleIO);
60
61         commandDispatcher.addLocalCommands(connectionManager, localSchema, cliArgs.getConnectionTimeoutMs());
62
63         switch (cliArgs.connectionArgsPresent()) {
64         case TCP: {
65             // FIXME support pure TCP
66             handleRunningException(new UnsupportedOperationException("PURE TCP CONNECTIONS ARE NOT SUPPORTED YET, USE SSH INSTEAD BY PROVIDING USERNAME AND PASSWORD AS WELL"));
67             return;
68         }
69         case SSH: {
70             writeStatus(consoleIO, "Connecting to %s via SSH. Please wait.", cliArgs.getAddress());
71             connectionManager.connectBlocking(cliArgs.getAddress(), getClientSshConfig(cliArgs));
72             break;
73         }
74         case NONE: {/* Do not connect initially */
75             writeStatus(consoleIO, "No initial connection. To connect use the connect command");
76         }
77         }
78
79         try {
80             new Cli(consoleIO, commandDispatcher, argumentHandlerRegistry, schemaContextRegistry).run();
81         } catch (final Exception e) {
82             // TODO Running exceptions have to be handled properly
83             handleRunningException(e);
84             System.exit(0);
85         }
86     }
87
88     private static NetconfClientConfigurationBuilder getClientConfig(final CliArgumentParser cliArgs) {
89         return NetconfClientConfigurationBuilder.create().withAddress(cliArgs.getServerAddress())
90                 .withConnectionTimeoutMillis(cliArgs.getConnectionTimeoutMs())
91                 .withReconnectStrategy(Connect.getReconnectStrategy())
92                 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP);
93     }
94
95     private static NetconfClientConfigurationBuilder getClientSshConfig(final CliArgumentParser cliArgs) {
96         return NetconfClientConfigurationBuilder.create().withAddress(cliArgs.getServerAddress())
97                 .withConnectionTimeoutMillis(cliArgs.getConnectionTimeoutMs())
98                 .withReconnectStrategy(Connect.getReconnectStrategy())
99                 .withAuthHandler(cliArgs.getCredentials())
100                 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH);
101     }
102
103     private static void handleStartupException(final IOException e) {
104         handleException(e, "Unable to initialize CLI");
105     }
106
107     private static void handleException(final Exception e, final String message) {
108         System.err.println(message);
109         e.printStackTrace(System.err);
110     }
111
112     private static void writeStatus(final ConsoleIO io, final String blueprint, final Object... args) {
113         try {
114             io.formatLn(blueprint, args);
115         } catch (final IOException e) {
116             handleStartupException(e);
117         }
118     }
119
120     private static void handleRunningException(final Exception e) {
121         handleException(e, "Unexpected CLI runtime exception");
122     }
123
124     private static final class CliArgumentParser {
125
126         public static final String USERNAME = "username";
127         public static final String PASSWORD = "password";
128         public static final String SERVER = "server";
129         public static final String PORT = "port";
130
131         public static final String CONNECT_TIMEOUT = "connectionTimeout";
132         public static final int DEFAULT_CONNECTION_TIMEOUT_MS = 50000;
133
134         private final ArgumentParser parser;
135         private Namespace parsed;
136
137         private CliArgumentParser() {
138             parser = ArgumentParsers.newArgumentParser("Netconf cli").defaultHelp(true)
139                     .description("Generic cli for netconf devices")
140                     .usage("Submit address + port for initial TCP connection (PURE TCP CONNECTIONS ARE NOT SUPPORTED YET)\n" +
141                             "Submit username + password in addition to address + port for initial SSH connection\n" +
142                             "If no arguments(or unexpected combination) is submitted, cli will be started without initial connection\n" +
143                             "To use with ODL controller, run with: java -jar netconf-cli-0.2.5-SNAPSHOT-executable.jar  --server localhost --port 1830 --username admin --password admin");
144
145             final ArgumentGroup tcpGroup = parser.addArgumentGroup("TCP")
146                     .description("Base arguments to initiate TCP connection right away");
147
148             tcpGroup.addArgument("--" + SERVER).help("Netconf device ip-address/domain name");
149             tcpGroup.addArgument("--" + PORT).type(Integer.class).help("Netconf device port");
150             tcpGroup.addArgument("--" + CONNECT_TIMEOUT)
151                     .type(Integer.class)
152                     .setDefault(DEFAULT_CONNECTION_TIMEOUT_MS)
153                     .help("Timeout(in ms) for connection to succeed, if the connection is not fully established by the time is up, " +
154                             "connection attempt is considered a failure. This attribute is not working as expected yet");
155
156             final ArgumentGroup sshGroup = parser.addArgumentGroup("SSH")
157                     .description("SSH credentials, if provided, initial connection will be attempted using SSH");
158
159             sshGroup.addArgument("--" + USERNAME).help("Username for SSH connection");
160             sshGroup.addArgument("--" + PASSWORD).help("Password for SSH connection");
161         }
162
163         public void parse(final String[] args) throws ArgumentParserException {
164             try {
165                 this.parsed = parser.parseArgs(args);
166             } catch (final ArgumentParserException e) {
167                 parser.handleError(e);
168                 throw e;
169             }
170         }
171
172         public InetSocketAddress getServerAddress() {
173             try {
174                 return new InetSocketAddress(InetAddress.getByName(getAddress()), getPort());
175             } catch (final UnknownHostException e) {
176                 throw new IllegalArgumentException(e);
177             }
178         }
179
180         private Integer getPort() {
181             checkParsed();
182             return parsed.getInt(PORT);
183         }
184
185         private String getAddress() {
186             checkParsed();
187             return getString(SERVER);
188         }
189
190         private Integer getConnectionTimeoutMs() {
191             checkParsed();
192             return parsed.getInt(CONNECT_TIMEOUT);
193         }
194
195         private void checkParsed() {
196             Preconditions.checkState(parsed != null, "No arguments were parsed yet");
197         }
198
199         public String getUsername() {
200             checkParsed();
201             return getString(USERNAME);
202         }
203
204         private String getString(final String key) {
205             return parsed.getString(key);
206         }
207
208         public LoginPassword getCredentials() {
209             return new LoginPassword(getUsername(), getPassword());
210         }
211
212         public String getPassword() {
213             checkParsed();
214             return getString(PASSWORD);
215         }
216
217         public InitialConnectionType connectionArgsPresent() {
218             if(getAddress() != null && getPort() != null) {
219                 if(getUsername() != null && getPassword() != null) {
220                     return InitialConnectionType.SSH;
221                 }
222                 return InitialConnectionType.TCP;
223             }
224             return InitialConnectionType.NONE;
225         }
226
227         enum InitialConnectionType {
228             TCP, SSH, NONE
229         }
230     }
231 }