blob: 359b04cf06df94a1cd3db8414890f1489829a279 [file] [log] [blame]
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -07001/*
gyewan.an8d918412019-01-30 11:53:10 +09002 * Copyright 2019-present Open Networking Foundation
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.netconf;
18
19import java.util.Set;
20import java.util.concurrent.CompletableFuture;
21
22import org.slf4j.Logger;
23
24import com.google.common.annotations.Beta;
25
26import static com.google.common.base.Preconditions.checkNotNull;
27import static org.slf4j.LoggerFactory.getLogger;
28
29
30/**
31 * Abstract netconf session implements methods common
32 * for different implementations of netconf session.
33 */
34public abstract class AbstractNetconfSession implements NetconfSession {
35
36 private static final Logger log = getLogger(AbstractNetconfSession.class);
37
38 private static final String ENDPATTERN = "]]>]]>";
39 private static final String MESSAGE_ID_STRING = "message-id";
40 private static final String NEW_LINE = "\n";
41 private static final String EQUAL = "=";
42 private static final String RPC_OPEN = "<rpc ";
43 private static final String RPC_CLOSE = "</rpc>";
44 private static final String GET_OPEN = "<get>";
45 private static final String GET_CLOSE = "</get>";
46 private static final String WITH_DEFAULT_OPEN = "<with-defaults ";
47 private static final String WITH_DEFAULT_CLOSE = "</with-defaults>";
48 private static final String DEFAULT_OPERATION_OPEN = "<default-operation>";
49 private static final String DEFAULT_OPERATION_CLOSE = "</default-operation>";
50 private static final String SUBTREE_FILTER_OPEN = "<filter type=\"subtree\">";
51 private static final String SUBTREE_FILTER_CLOSE = "</filter>";
52 private static final String EDIT_CONFIG_OPEN = "<edit-config>";
53 private static final String EDIT_CONFIG_CLOSE = "</edit-config>";
gyewan.an8d918412019-01-30 11:53:10 +090054 private static final String GET_CONFIG_OPEN = "<get-config>";
55 private static final String GET_CONFIG_CLOSE = "</get-config>";
56 private static final String COPY_CONFIG_OPEN = "<copy-config>";
57 private static final String COPY_CONFIG_CLOSE = "</copy-config>";
58 private static final String DELETE_CONFIG_OPEN = "<delete-config>";
59 private static final String DELETE_CONFIG_CLOSE = "</delete-config>";
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -070060 private static final String TARGET_OPEN = "<target>";
61 private static final String TARGET_CLOSE = "</target>";
gyewan.an8d918412019-01-30 11:53:10 +090062 private static final String SOURCE_OPEN = "<source>";
63 private static final String SOURCE_CLOSE = "</source>";
64 private static final String LOCK_OPEN = "<lock>";
65 private static final String LOCK_CLOSE = "</lock>";
66 private static final String UNLOCK_OPEN = "<lock>";
67 private static final String UNLOCK_CLOSE = "</lock>";
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -070068 // FIXME hard coded namespace nc
69 private static final String CONFIG_OPEN = "<config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
70 private static final String CONFIG_CLOSE = "</config>";
71 private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
72 private static final String NETCONF_BASE_NAMESPACE =
73 "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"";
74 private static final String NETCONF_WITH_DEFAULTS_NAMESPACE =
75 "xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults\"";
76
77 @Override
78 public abstract CompletableFuture<String> request(String request) throws NetconfException;
79
80 @Override
81 public abstract CompletableFuture<String> rpc(String request) throws NetconfException;
82
gyewan.an91d7e7e2019-01-17 15:12:48 +090083 protected CompletableFuture<CharSequence> executeRpc(String rpcString) throws NetconfException {
84 return rpc(rpcString)
85 .thenApply(msg -> {
86 // crude way of removing rpc-reply envelope
87 int begin = msg.indexOf("<data>");
88 int end = msg.lastIndexOf("</data>");
89 if (begin != -1 && end != -1) {
90 return msg.subSequence(begin, end + "</data>".length());
91 } else {
92 // FIXME probably should exceptionally fail here.
93 return msg;
94 }
95 });
96 }
97
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -070098 @Override
99 public CompletableFuture<CharSequence> asyncGetConfig(DatastoreId datastore) throws NetconfException {
100 StringBuilder rpc = new StringBuilder();
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700101
gyewan.an8d918412019-01-30 11:53:10 +0900102 rpc.append(RPC_OPEN);
103 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
104 rpc.append(GET_CONFIG_OPEN).append(NEW_LINE);
105 rpc.append(SOURCE_OPEN).append(NEW_LINE);
106
107 rpc.append('<').append(checkNotNull(datastore)).append("/>");
108
109 rpc.append(SOURCE_CLOSE).append(NEW_LINE);
110 // filter here
111 rpc.append(GET_CONFIG_CLOSE).append(NEW_LINE);
112 rpc.append(RPC_CLOSE);
113
114 return executeRpc(rpc.toString());
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700115 }
116
117 @Override
118 public CompletableFuture<CharSequence> asyncGet() throws NetconfException {
119 StringBuilder rpc = new StringBuilder();
gyewan.an8d918412019-01-30 11:53:10 +0900120 rpc.append(RPC_OPEN);
121 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
122 rpc.append(GET_OPEN).append(NEW_LINE);
123 //filter here
124 rpc.append(GET_CLOSE).append(NEW_LINE);
125 rpc.append(RPC_CLOSE).append(NEW_LINE);
126
127 return executeRpc(rpc.toString());
128 }
129
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700130 @Override
131 public String get(String request) throws NetconfException {
132 return requestSync(request);
133 }
134
135 @Override
136 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
137 StringBuilder rpc = new StringBuilder(XML_HEADER);
138 rpc.append(RPC_OPEN);
139 rpc.append(MESSAGE_ID_STRING);
140 rpc.append(EQUAL);
141 rpc.append("\"");
142 //Assign a random integer here, it will be replaced in formatting
143 rpc.append(1);
144 rpc.append("\" ");
145 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
146 rpc.append(GET_OPEN).append(NEW_LINE);
147 if (filterSchema != null) {
148 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
149 rpc.append(filterSchema).append(NEW_LINE);
150 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
151 }
152 if (withDefaultsMode != null) {
153 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
154 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
155 }
156 rpc.append(GET_CLOSE).append(NEW_LINE);
157 rpc.append(RPC_CLOSE).append(NEW_LINE);
158 rpc.append(ENDPATTERN);
159 return requestSync(rpc.toString());
160 }
161
162 @Override
163 public String doWrappedRpc(String request) throws NetconfException {
164 StringBuilder rpc = new StringBuilder(XML_HEADER);
165 rpc.append(RPC_OPEN);
166 rpc.append(MESSAGE_ID_STRING);
167 rpc.append(EQUAL);
168 rpc.append("\"");
169 //Assign a random integer here, it will be replaced in formatting
170 rpc.append(1);
171 rpc.append("\" ");
172 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
173 rpc.append(request);
174 rpc.append(RPC_CLOSE).append(NEW_LINE);
175 rpc.append(ENDPATTERN);
176 return requestSync(rpc.toString());
177 }
178
179 @Override
180 public abstract String requestSync(String request) throws NetconfException;
181
182 @Override
183 public String getConfig(DatastoreId netconfTargetConfig) throws NetconfException {
184 return getConfig(netconfTargetConfig, null);
185 }
186
187 @Override
188 public String getConfig(DatastoreId netconfTargetConfig, String configurationFilterSchema) throws NetconfException {
189 StringBuilder rpc = new StringBuilder(XML_HEADER);
gyewan.an8d918412019-01-30 11:53:10 +0900190 rpc.append(RPC_OPEN);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700191 rpc.append(MESSAGE_ID_STRING);
192 rpc.append(EQUAL);
193 rpc.append("\"");
194 //Assign a random integer here, it will be replaced in formatting
195 rpc.append(1);
196 rpc.append("\" ");
gyewan.an8d918412019-01-30 11:53:10 +0900197 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
198 rpc.append(GET_CONFIG_OPEN).append(NEW_LINE);
199 rpc.append(SOURCE_OPEN).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700200 rpc.append("<").append(netconfTargetConfig).append("/>");
gyewan.an8d918412019-01-30 11:53:10 +0900201 rpc.append(SOURCE_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700202 if (configurationFilterSchema != null) {
gyewan.an8d918412019-01-30 11:53:10 +0900203 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
204 rpc.append(configurationFilterSchema).append(NEW_LINE);
205 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700206 }
gyewan.an8d918412019-01-30 11:53:10 +0900207 rpc.append(GET_CONFIG_CLOSE).append(NEW_LINE);
208 rpc.append(RPC_CLOSE).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700209 rpc.append(ENDPATTERN);
210 String reply = requestSync(rpc.toString());
211 return checkReply(reply) ? reply : "ERROR " + reply;
212 }
213
214 @Override
215 public boolean editConfig(String newConfiguration) throws NetconfException {
216 if (!newConfiguration.endsWith(ENDPATTERN)) {
217 newConfiguration = newConfiguration + ENDPATTERN;
218 }
219 return checkReply(requestSync(newConfiguration));
220 }
221
222 @Override
223 public boolean editConfig(DatastoreId netconfTargetConfig,
224 String mode,
225 String newConfiguration) throws NetconfException {
226 newConfiguration = newConfiguration.trim();
227 StringBuilder rpc = new StringBuilder(XML_HEADER);
228 rpc.append(RPC_OPEN);
229 rpc.append(MESSAGE_ID_STRING);
230 rpc.append(EQUAL);
231 rpc.append("\"");
232 //Assign a random integer here, it will be replaced in formatting
233 rpc.append(1);
234 rpc.append("\" ");
235 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
gyewan.an8d918412019-01-30 11:53:10 +0900236 rpc.append(EDIT_CONFIG_OPEN).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700237 rpc.append(TARGET_OPEN);
238 rpc.append("<").append(netconfTargetConfig).append("/>");
gyewan.an8d918412019-01-30 11:53:10 +0900239 rpc.append(TARGET_CLOSE).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700240 if (mode != null) {
241 rpc.append(DEFAULT_OPERATION_OPEN);
242 rpc.append(mode);
gyewan.an8d918412019-01-30 11:53:10 +0900243 rpc.append(DEFAULT_OPERATION_CLOSE).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700244 }
gyewan.an8d918412019-01-30 11:53:10 +0900245 rpc.append(CONFIG_OPEN).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700246 rpc.append(newConfiguration);
gyewan.an8d918412019-01-30 11:53:10 +0900247 rpc.append(CONFIG_CLOSE).append(NEW_LINE);
248 rpc.append(EDIT_CONFIG_CLOSE).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700249 rpc.append(RPC_CLOSE);
250 rpc.append(ENDPATTERN);
251 String reply = requestSync(rpc.toString());
252 return checkReply(reply);
253 }
254
255 @Override
256 public boolean copyConfig(DatastoreId destination, DatastoreId source) throws NetconfException {
257 return checkReply(requestSync(bareCopyConfig(destination.asXml(), source.asXml())));
258 }
259
260 @Override
261 public boolean copyConfig(DatastoreId netconfTargetConfig, String newConfiguration) throws NetconfException {
262 return checkReply(requestSync(bareCopyConfig(netconfTargetConfig.asXml(),
263 normalizeCopyConfigParam(newConfiguration))));
264 }
265
266 @Override
267 public boolean copyConfig(String netconfTargetConfig, String newConfiguration) throws NetconfException {
268 return checkReply(requestSync(bareCopyConfig(normalizeCopyConfigParam(netconfTargetConfig),
269 normalizeCopyConfigParam(newConfiguration))));
270 }
271
272 @Override
273 public boolean deleteConfig(DatastoreId netconfTargetConfig) throws NetconfException {
274 if (netconfTargetConfig.equals(DatastoreId.RUNNING)) {
275 return false;
276 }
277 StringBuilder rpc = new StringBuilder(XML_HEADER);
gyewan.an8d918412019-01-30 11:53:10 +0900278 rpc.append(RPC_OPEN).append(">");
279 rpc.append(DELETE_CONFIG_OPEN);
280 rpc.append(TARGET_OPEN);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700281 rpc.append("<").append(netconfTargetConfig).append("/>");
gyewan.an8d918412019-01-30 11:53:10 +0900282 rpc.append(TARGET_CLOSE);
283 rpc.append(DELETE_CONFIG_CLOSE);
284 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700285 rpc.append(ENDPATTERN);
286 return checkReply(requestSync(rpc.toString()));
287 }
288
289 @Override
290 public void startSubscription() throws NetconfException {
291 startSubscription(null);
292 }
293
294 @Override
295 public abstract void startSubscription(String filterSchema) throws NetconfException;
296
297 @Override
298 public abstract void endSubscription() throws NetconfException;
299
300 @Override
301 public boolean lock(DatastoreId datastore) throws NetconfException {
302 StringBuilder rpc = new StringBuilder(XML_HEADER);
gyewan.an8d918412019-01-30 11:53:10 +0900303 rpc.append(RPC_OPEN);
304 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
305 rpc.append(LOCK_OPEN);
306 rpc.append(TARGET_OPEN);
307 rpc.append("<").append(datastore.id()).append("/>");
308 rpc.append(TARGET_CLOSE);
309 rpc.append(LOCK_CLOSE);
310 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700311 rpc.append(ENDPATTERN);
gyewan.an8d918412019-01-30 11:53:10 +0900312
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700313 String lockReply = requestSync(rpc.toString());
314 return checkReply(lockReply);
315 }
316
317 @Override
318 public boolean unlock(DatastoreId datastore) throws NetconfException {
319 StringBuilder rpc = new StringBuilder(XML_HEADER);
gyewan.an8d918412019-01-30 11:53:10 +0900320 rpc.append(RPC_OPEN);
321 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
322 rpc.append(UNLOCK_OPEN);
323 rpc.append(TARGET_OPEN);
324 rpc.append("<").append(datastore.id()).append("/>");
325 rpc.append(TARGET_CLOSE);
326 rpc.append(UNLOCK_CLOSE);
327 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700328 rpc.append(ENDPATTERN);
329 String unlockReply = requestSync(rpc.toString());
330 return checkReply(unlockReply);
331 }
332
333 @Override
Andrea Campanella3a361452019-08-02 10:17:53 +0200334 public boolean commit() throws NetconfException {
335 String rpc = "<rpc message-id=\"101\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"> <commit/></rpc>";
336 String reply = requestSync(rpc);
337 if (!checkReply(reply)) {
338 throw new NetconfException("Request not successful, with reply " + reply);
339 }
340 return true;
341 }
342
343 @Override
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700344 public boolean close() throws NetconfException {
345 StringBuilder rpc = new StringBuilder();
gyewan.an8d918412019-01-30 11:53:10 +0900346 rpc.append(RPC_OPEN);
347 rpc.append(NETCONF_BASE_NAMESPACE).append(">");
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700348 rpc.append("<close-session/>");
gyewan.an8d918412019-01-30 11:53:10 +0900349 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700350 rpc.append(ENDPATTERN);
351 boolean closed = checkReply(requestSync(rpc.toString()));
352 if (closed) {
353 return closed;
354 } else {
355 //forcefully kill session if not closed
356 rpc = new StringBuilder();
gyewan.an8d918412019-01-30 11:53:10 +0900357 rpc.append(RPC_OPEN);
358 rpc.append(NETCONF_BASE_NAMESPACE).append(">");
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700359 rpc.append("<kill-session/>");
gyewan.an8d918412019-01-30 11:53:10 +0900360 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700361 rpc.append(ENDPATTERN);
362 return checkReply(requestSync(rpc.toString()));
363 }
364 }
365
366 @Override
367 public abstract String getSessionId();
368
369 @Override
370 public abstract Set<String> getDeviceCapabilitiesSet();
371
372 @Override
gyewan.an91d7e7e2019-01-17 15:12:48 +0900373 public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) throws NetconfException {
374 throw new NetconfException("Only master session can call addDeviceOutputListener");
375 }
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700376
377 @Override
gyewan.an91d7e7e2019-01-17 15:12:48 +0900378 public void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener) throws NetconfException {
379 throw new NetconfException("Only master session can call removeDeviceOutputListener");
380 }
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700381
382 /**
383 * Checks errors in reply from the session.
384 * @param reply reply string
385 * @return true if no error, else false
386 */
387 @Beta
388 protected boolean checkReply(String reply) {
389 if (reply != null) {
390 if (!reply.contains("<rpc-error>")) {
391 return true;
392 } else if (reply.contains("<ok/>")
393 || (reply.contains("<rpc-error>")
394 && reply.contains("warning"))) {
395 // FIXME rpc-error with a warning is considered same as Ok??
396 return true;
397 }
398 }
399 return false;
400 }
401
402 private String bareCopyConfig(CharSequence target,
403 CharSequence source)
404 throws NetconfException {
405 StringBuilder rpc = new StringBuilder(XML_HEADER);
406 rpc.append(RPC_OPEN);
407 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
gyewan.an8d918412019-01-30 11:53:10 +0900408 rpc.append(COPY_CONFIG_OPEN);
409 rpc.append(TARGET_OPEN);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700410 rpc.append(target);
gyewan.an8d918412019-01-30 11:53:10 +0900411 rpc.append(TARGET_CLOSE);
412 rpc.append(SOURCE_OPEN);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700413 rpc.append(source);
gyewan.an8d918412019-01-30 11:53:10 +0900414 rpc.append(SOURCE_CLOSE);
415 rpc.append(COPY_CONFIG_CLOSE);
416 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700417 rpc.append(ENDPATTERN);
418 return rpc.toString();
419 }
420
421 /**
422 * Normalize String parameter passed to copy-config API.
423 * <p>
424 * Provided for backward compatibility purpose
425 *
426 * @param input passed to copyConfig API
427 * @return XML likely to be suitable for copy-config source or target
428 */
429 private CharSequence normalizeCopyConfigParam(String input) {
430 input = input.trim();
431 if (input.startsWith("<url")) {
432 return input;
433 } else if (!input.startsWith("<")) {
434 // assume it is a datastore name
435 return DatastoreId.datastore(input).asXml();
436 } else if (!input.startsWith("<config>")) {
437 return "<config>" + input + "</config>";
438 }
439 return input;
440 }
441}