blob: 22545721aaf7133972b93cf3f0d3724b40bbbfe4 [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
83 @Override
84 public CompletableFuture<CharSequence> asyncGetConfig(DatastoreId datastore) throws NetconfException {
85 StringBuilder rpc = new StringBuilder();
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -070086
gyewan.an8d918412019-01-30 11:53:10 +090087 rpc.append(RPC_OPEN);
88 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
89 rpc.append(GET_CONFIG_OPEN).append(NEW_LINE);
90 rpc.append(SOURCE_OPEN).append(NEW_LINE);
91
92 rpc.append('<').append(checkNotNull(datastore)).append("/>");
93
94 rpc.append(SOURCE_CLOSE).append(NEW_LINE);
95 // filter here
96 rpc.append(GET_CONFIG_CLOSE).append(NEW_LINE);
97 rpc.append(RPC_CLOSE);
98
99 return executeRpc(rpc.toString());
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700100 }
101
102 @Override
103 public CompletableFuture<CharSequence> asyncGet() throws NetconfException {
104 StringBuilder rpc = new StringBuilder();
gyewan.an8d918412019-01-30 11:53:10 +0900105
106 rpc.append(RPC_OPEN);
107 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
108 rpc.append(GET_OPEN).append(NEW_LINE);
109 //filter here
110 rpc.append(GET_CLOSE).append(NEW_LINE);
111 rpc.append(RPC_CLOSE).append(NEW_LINE);
112
113 return executeRpc(rpc.toString());
114 }
115
116 protected CompletableFuture<CharSequence> executeRpc(String rpcString) throws NetconfException {
117 return rpc(rpcString)
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700118 .thenApply(msg -> {
119 // crude way of removing rpc-reply envelope
120 int begin = msg.indexOf("<data>");
121 int end = msg.lastIndexOf("</data>");
122 if (begin != -1 && end != -1) {
123 return msg.subSequence(begin, end + "</data>".length());
124 } else {
125 // FIXME probably should exceptionally fail here.
126 return msg;
127 }
128 });
129
130 }
131
132 @Override
133 public String get(String request) throws NetconfException {
134 return requestSync(request);
135 }
136
137 @Override
138 public String get(String filterSchema, String withDefaultsMode) throws NetconfException {
139 StringBuilder rpc = new StringBuilder(XML_HEADER);
140 rpc.append(RPC_OPEN);
141 rpc.append(MESSAGE_ID_STRING);
142 rpc.append(EQUAL);
143 rpc.append("\"");
144 //Assign a random integer here, it will be replaced in formatting
145 rpc.append(1);
146 rpc.append("\" ");
147 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
148 rpc.append(GET_OPEN).append(NEW_LINE);
149 if (filterSchema != null) {
150 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
151 rpc.append(filterSchema).append(NEW_LINE);
152 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
153 }
154 if (withDefaultsMode != null) {
155 rpc.append(WITH_DEFAULT_OPEN).append(NETCONF_WITH_DEFAULTS_NAMESPACE).append(">");
156 rpc.append(withDefaultsMode).append(WITH_DEFAULT_CLOSE).append(NEW_LINE);
157 }
158 rpc.append(GET_CLOSE).append(NEW_LINE);
159 rpc.append(RPC_CLOSE).append(NEW_LINE);
160 rpc.append(ENDPATTERN);
161 return requestSync(rpc.toString());
162 }
163
164 @Override
165 public String doWrappedRpc(String request) throws NetconfException {
166 StringBuilder rpc = new StringBuilder(XML_HEADER);
167 rpc.append(RPC_OPEN);
168 rpc.append(MESSAGE_ID_STRING);
169 rpc.append(EQUAL);
170 rpc.append("\"");
171 //Assign a random integer here, it will be replaced in formatting
172 rpc.append(1);
173 rpc.append("\" ");
174 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
175 rpc.append(request);
176 rpc.append(RPC_CLOSE).append(NEW_LINE);
177 rpc.append(ENDPATTERN);
178 return requestSync(rpc.toString());
179 }
180
181 @Override
182 public abstract String requestSync(String request) throws NetconfException;
183
184 @Override
185 public String getConfig(DatastoreId netconfTargetConfig) throws NetconfException {
186 return getConfig(netconfTargetConfig, null);
187 }
188
189 @Override
190 public String getConfig(DatastoreId netconfTargetConfig, String configurationFilterSchema) throws NetconfException {
191 StringBuilder rpc = new StringBuilder(XML_HEADER);
gyewan.an8d918412019-01-30 11:53:10 +0900192 rpc.append(RPC_OPEN);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700193 rpc.append(MESSAGE_ID_STRING);
194 rpc.append(EQUAL);
195 rpc.append("\"");
196 //Assign a random integer here, it will be replaced in formatting
197 rpc.append(1);
198 rpc.append("\" ");
gyewan.an8d918412019-01-30 11:53:10 +0900199 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
200 rpc.append(GET_CONFIG_OPEN).append(NEW_LINE);
201 rpc.append(SOURCE_OPEN).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700202 rpc.append("<").append(netconfTargetConfig).append("/>");
gyewan.an8d918412019-01-30 11:53:10 +0900203 rpc.append(SOURCE_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700204 if (configurationFilterSchema != null) {
gyewan.an8d918412019-01-30 11:53:10 +0900205 rpc.append(SUBTREE_FILTER_OPEN).append(NEW_LINE);
206 rpc.append(configurationFilterSchema).append(NEW_LINE);
207 rpc.append(SUBTREE_FILTER_CLOSE).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700208 }
gyewan.an8d918412019-01-30 11:53:10 +0900209 rpc.append(GET_CONFIG_CLOSE).append(NEW_LINE);
210 rpc.append(RPC_CLOSE).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700211 rpc.append(ENDPATTERN);
212 String reply = requestSync(rpc.toString());
213 return checkReply(reply) ? reply : "ERROR " + reply;
214 }
215
216 @Override
217 public boolean editConfig(String newConfiguration) throws NetconfException {
218 if (!newConfiguration.endsWith(ENDPATTERN)) {
219 newConfiguration = newConfiguration + ENDPATTERN;
220 }
221 return checkReply(requestSync(newConfiguration));
222 }
223
224 @Override
225 public boolean editConfig(DatastoreId netconfTargetConfig,
226 String mode,
227 String newConfiguration) throws NetconfException {
228 newConfiguration = newConfiguration.trim();
229 StringBuilder rpc = new StringBuilder(XML_HEADER);
230 rpc.append(RPC_OPEN);
231 rpc.append(MESSAGE_ID_STRING);
232 rpc.append(EQUAL);
233 rpc.append("\"");
234 //Assign a random integer here, it will be replaced in formatting
235 rpc.append(1);
236 rpc.append("\" ");
237 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
gyewan.an8d918412019-01-30 11:53:10 +0900238 rpc.append(EDIT_CONFIG_OPEN).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700239 rpc.append(TARGET_OPEN);
240 rpc.append("<").append(netconfTargetConfig).append("/>");
gyewan.an8d918412019-01-30 11:53:10 +0900241 rpc.append(TARGET_CLOSE).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700242 if (mode != null) {
243 rpc.append(DEFAULT_OPERATION_OPEN);
244 rpc.append(mode);
gyewan.an8d918412019-01-30 11:53:10 +0900245 rpc.append(DEFAULT_OPERATION_CLOSE).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700246 }
gyewan.an8d918412019-01-30 11:53:10 +0900247 rpc.append(CONFIG_OPEN).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700248 rpc.append(newConfiguration);
gyewan.an8d918412019-01-30 11:53:10 +0900249 rpc.append(CONFIG_CLOSE).append(NEW_LINE);
250 rpc.append(EDIT_CONFIG_CLOSE).append(NEW_LINE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700251 rpc.append(RPC_CLOSE);
252 rpc.append(ENDPATTERN);
253 String reply = requestSync(rpc.toString());
254 return checkReply(reply);
255 }
256
257 @Override
258 public boolean copyConfig(DatastoreId destination, DatastoreId source) throws NetconfException {
259 return checkReply(requestSync(bareCopyConfig(destination.asXml(), source.asXml())));
260 }
261
262 @Override
263 public boolean copyConfig(DatastoreId netconfTargetConfig, String newConfiguration) throws NetconfException {
264 return checkReply(requestSync(bareCopyConfig(netconfTargetConfig.asXml(),
265 normalizeCopyConfigParam(newConfiguration))));
266 }
267
268 @Override
269 public boolean copyConfig(String netconfTargetConfig, String newConfiguration) throws NetconfException {
270 return checkReply(requestSync(bareCopyConfig(normalizeCopyConfigParam(netconfTargetConfig),
271 normalizeCopyConfigParam(newConfiguration))));
272 }
273
274 @Override
275 public boolean deleteConfig(DatastoreId netconfTargetConfig) throws NetconfException {
276 if (netconfTargetConfig.equals(DatastoreId.RUNNING)) {
277 return false;
278 }
279 StringBuilder rpc = new StringBuilder(XML_HEADER);
gyewan.an8d918412019-01-30 11:53:10 +0900280 rpc.append(RPC_OPEN).append(">");
281 rpc.append(DELETE_CONFIG_OPEN);
282 rpc.append(TARGET_OPEN);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700283 rpc.append("<").append(netconfTargetConfig).append("/>");
gyewan.an8d918412019-01-30 11:53:10 +0900284 rpc.append(TARGET_CLOSE);
285 rpc.append(DELETE_CONFIG_CLOSE);
286 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700287 rpc.append(ENDPATTERN);
288 return checkReply(requestSync(rpc.toString()));
289 }
290
291 @Override
292 public void startSubscription() throws NetconfException {
293 startSubscription(null);
294 }
295
296 @Override
297 public abstract void startSubscription(String filterSchema) throws NetconfException;
298
299 @Override
300 public abstract void endSubscription() throws NetconfException;
301
302 @Override
303 public boolean lock(DatastoreId datastore) throws NetconfException {
304 StringBuilder rpc = new StringBuilder(XML_HEADER);
gyewan.an8d918412019-01-30 11:53:10 +0900305 rpc.append(RPC_OPEN);
306 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
307 rpc.append(LOCK_OPEN);
308 rpc.append(TARGET_OPEN);
309 rpc.append("<").append(datastore.id()).append("/>");
310 rpc.append(TARGET_CLOSE);
311 rpc.append(LOCK_CLOSE);
312 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700313 rpc.append(ENDPATTERN);
gyewan.an8d918412019-01-30 11:53:10 +0900314
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700315 String lockReply = requestSync(rpc.toString());
316 return checkReply(lockReply);
317 }
318
319 @Override
320 public boolean unlock(DatastoreId datastore) throws NetconfException {
321 StringBuilder rpc = new StringBuilder(XML_HEADER);
gyewan.an8d918412019-01-30 11:53:10 +0900322 rpc.append(RPC_OPEN);
323 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
324 rpc.append(UNLOCK_OPEN);
325 rpc.append(TARGET_OPEN);
326 rpc.append("<").append(datastore.id()).append("/>");
327 rpc.append(TARGET_CLOSE);
328 rpc.append(UNLOCK_CLOSE);
329 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700330 rpc.append(ENDPATTERN);
331 String unlockReply = requestSync(rpc.toString());
332 return checkReply(unlockReply);
333 }
334
335 @Override
336 public boolean close() throws NetconfException {
337 StringBuilder rpc = new StringBuilder();
gyewan.an8d918412019-01-30 11:53:10 +0900338 rpc.append(RPC_OPEN);
339 rpc.append(NETCONF_BASE_NAMESPACE).append(">");
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700340 rpc.append("<close-session/>");
gyewan.an8d918412019-01-30 11:53:10 +0900341 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700342 rpc.append(ENDPATTERN);
343 boolean closed = checkReply(requestSync(rpc.toString()));
344 if (closed) {
345 return closed;
346 } else {
347 //forcefully kill session if not closed
348 rpc = new StringBuilder();
gyewan.an8d918412019-01-30 11:53:10 +0900349 rpc.append(RPC_OPEN);
350 rpc.append(NETCONF_BASE_NAMESPACE).append(">");
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700351 rpc.append("<kill-session/>");
gyewan.an8d918412019-01-30 11:53:10 +0900352 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700353 rpc.append(ENDPATTERN);
354 return checkReply(requestSync(rpc.toString()));
355 }
356 }
357
358 @Override
359 public abstract String getSessionId();
360
361 @Override
362 public abstract Set<String> getDeviceCapabilitiesSet();
363
364 @Override
365 public abstract void addDeviceOutputListener(NetconfDeviceOutputEventListener listener);
366
367 @Override
368 public abstract void removeDeviceOutputListener(NetconfDeviceOutputEventListener listener);
369
370 /**
371 * Checks errors in reply from the session.
372 * @param reply reply string
373 * @return true if no error, else false
374 */
375 @Beta
376 protected boolean checkReply(String reply) {
377 if (reply != null) {
378 if (!reply.contains("<rpc-error>")) {
379 return true;
380 } else if (reply.contains("<ok/>")
381 || (reply.contains("<rpc-error>")
382 && reply.contains("warning"))) {
383 // FIXME rpc-error with a warning is considered same as Ok??
384 return true;
385 }
386 }
387 return false;
388 }
389
390 private String bareCopyConfig(CharSequence target,
391 CharSequence source)
392 throws NetconfException {
393 StringBuilder rpc = new StringBuilder(XML_HEADER);
394 rpc.append(RPC_OPEN);
395 rpc.append(NETCONF_BASE_NAMESPACE).append(">\n");
gyewan.an8d918412019-01-30 11:53:10 +0900396 rpc.append(COPY_CONFIG_OPEN);
397 rpc.append(TARGET_OPEN);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700398 rpc.append(target);
gyewan.an8d918412019-01-30 11:53:10 +0900399 rpc.append(TARGET_CLOSE);
400 rpc.append(SOURCE_OPEN);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700401 rpc.append(source);
gyewan.an8d918412019-01-30 11:53:10 +0900402 rpc.append(SOURCE_CLOSE);
403 rpc.append(COPY_CONFIG_CLOSE);
404 rpc.append(RPC_CLOSE);
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700405 rpc.append(ENDPATTERN);
406 return rpc.toString();
407 }
408
409 /**
410 * Normalize String parameter passed to copy-config API.
411 * <p>
412 * Provided for backward compatibility purpose
413 *
414 * @param input passed to copyConfig API
415 * @return XML likely to be suitable for copy-config source or target
416 */
417 private CharSequence normalizeCopyConfigParam(String input) {
418 input = input.trim();
419 if (input.startsWith("<url")) {
420 return input;
421 } else if (!input.startsWith("<")) {
422 // assume it is a datastore name
423 return DatastoreId.datastore(input).asXml();
424 } else if (!input.startsWith("<config>")) {
425 return "<config>" + input + "</config>";
426 }
427 return input;
428 }
429}