blob: 0abffe59e767f99b2f3aefb0fecebbeb4fa3c5ed [file] [log] [blame]
fahadnaeemkhan71827242017-09-21 15:10:07 -07001/*
2 * Copyright 2016-present Open Networking Foundation
3 *
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 */
Jeff Groom725ed542018-04-07 14:41:30 -060016package org.onosproject.drivers.ciena.waveserver.rest;
fahadnaeemkhan71827242017-09-21 15:10:07 -070017
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -080018import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.ObjectMapper;
20import com.fasterxml.jackson.databind.ObjectReader;
21import com.google.common.collect.ImmutableList;
22import com.google.common.collect.Lists;
23import org.apache.commons.lang3.tuple.Pair;
fahadnaeemkhan71827242017-09-21 15:10:07 -070024import org.onlab.util.Frequency;
fahadnaeemkhanebe2ab72018-02-16 17:42:45 -080025import org.onlab.util.Spectrum;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070026import org.onosproject.driver.optical.flowrule.CrossConnectCache;
Thomas Vachuska52f2cd12018-11-08 21:20:04 -080027import org.onosproject.alarm.Alarm;
28import org.onosproject.alarm.AlarmEntityId;
29import org.onosproject.alarm.AlarmId;
30import org.onosproject.alarm.DefaultAlarm;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070031import org.onosproject.net.ChannelSpacing;
fahadnaeemkhan71827242017-09-21 15:10:07 -070032import org.onosproject.net.DeviceId;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070033import org.onosproject.net.OchSignal;
34import org.onosproject.net.OchSignalType;
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -080035import org.onosproject.net.Port;
36import org.onosproject.net.PortNumber;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070037import org.onosproject.net.device.DeviceService;
fahadnaeemkhan71827242017-09-21 15:10:07 -070038import org.onosproject.net.driver.DriverHandler;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070039import org.onosproject.net.flow.DefaultFlowEntry;
40import org.onosproject.net.flow.DefaultFlowRule;
41import org.onosproject.net.flow.DefaultTrafficSelector;
42import org.onosproject.net.flow.DefaultTrafficTreatment;
43import org.onosproject.net.flow.FlowEntry;
44import org.onosproject.net.flow.FlowId;
45import org.onosproject.net.flow.FlowRule;
46import org.onosproject.net.flow.TrafficSelector;
47import org.onosproject.net.flow.TrafficTreatment;
48import org.onosproject.net.flow.criteria.Criteria;
fahadnaeemkhan71827242017-09-21 15:10:07 -070049import org.onosproject.protocol.rest.RestSBController;
50import org.slf4j.Logger;
51
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -080052import javax.ws.rs.core.MediaType;
53import javax.ws.rs.core.Response;
54import java.io.ByteArrayInputStream;
55import java.io.IOException;
56import java.io.InputStream;
57import java.nio.charset.StandardCharsets;
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -070058import java.time.LocalDateTime;
59import java.time.ZoneId;
60import java.time.format.DateTimeFormatter;
61import java.time.format.DateTimeParseException;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070062import java.util.Collection;
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -080063import java.util.List;
64import java.util.Objects;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070065import java.util.stream.Collectors;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070066
fahadnaeemkhan71827242017-09-21 15:10:07 -070067import static com.google.common.base.Preconditions.checkNotNull;
68import static org.slf4j.LoggerFactory.getLogger;
69
70public class CienaRestDevice {
fahadnaeemkhana37be5c2018-02-15 18:39:55 -080071 private static final Frequency CENTER_FREQUENCY = Frequency.ofGHz(195_950);
fahadnaeemkhan71827242017-09-21 15:10:07 -070072 private static final String ENABLED = "enabled";
73 private static final String DISABLED = "disabled";
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070074 private static final String VALUE = "value";
75 private static final String STATE = "state";
76 private static final String ADMIN_STATE = "admin-state";
77 private static final String WAVELENGTH = "wavelength";
78 private static final String DATA = "data";
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -070079 private static final String ACTIVE = "active";
80 private static final String ACKNOWLEDGE = "acknowledged";
81 private static final String SEVERITY = "severity";
82 private static final String DESCRIPTION = "description";
83 private static final String INSTANCE = "instance";
84 private static final String PORT = "port";
85 private static final String PTP = "ptp";
86 private static final String UTC = "UTC";
87 private static final String OTHER = "other";
88 private static final String DATE_TIME_FORMAT = "EEE MMM [ ]d HH:mm:ss yyyy";
89 //keys
90 private static final String ALARM_KEY = "ws-alarms";
91 private static final String ALARM_INSTANCE_ID = "alarm-instance-id";
92 private static final String ALARM_TABLE_ID = "alarm-table-id";
93 private static final String ALARM_LOCAL_DATE_TIME = "local-date-time";
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070094 private static final String LINE_SYSTEM_CHANNEL_NUMBER = "ciena-ws-ptp-modem:line-system-channel-number";
95 private static final String FREQUENCY_KEY = "ciena-ws-ptp-modem:frequency";
fahadnaeemkhan71827242017-09-21 15:10:07 -070096 //URIs
97 private static final String PORT_URI = "ws-ptps/ptps/%s";
98 private static final String TRANSMITTER_URI = PORT_URI + "/properties/transmitter";
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070099 private static final String PORT_STATE_URI = PORT_URI + "/" + STATE;
100 private static final String WAVELENGTH_URI = TRANSMITTER_URI + "/" + WAVELENGTH;
101 private static final String FREQUENCY_URI = TRANSMITTER_URI + "/" + FREQUENCY_KEY;
102 private static final String CHANNEL_URI = TRANSMITTER_URI + "/" + LINE_SYSTEM_CHANNEL_NUMBER;
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700103 private static final String ACTIVE_ALARMS_URL = ALARM_KEY + "/" + ACTIVE;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700104 private static final List<String> LINESIDE_PORT_ID = ImmutableList.of("4", "48");
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800105 private static final ChannelSpacing CHANNEL_SPACING = ChannelSpacing.CHL_50GHZ;
fahadnaeemkhan71827242017-09-21 15:10:07 -0700106
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700107 private final Logger log = getLogger(getClass());
108
109 private DeviceId deviceId;
110 private RestSBController controller;
111 private CrossConnectCache crossConnectCache;
112 private DeviceService deviceService;
113
114
fahadnaeemkhan71827242017-09-21 15:10:07 -0700115 public CienaRestDevice(DriverHandler handler) throws NullPointerException {
116 deviceId = handler.data().deviceId();
117 controller = checkNotNull(handler.get(RestSBController.class));
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700118 crossConnectCache = checkNotNull(handler.get(CrossConnectCache.class));
119 deviceService = checkNotNull(handler.get(DeviceService.class));
120 }
121
122 /**
123 * return the Line side ports.
124 *
125 * @return List of Line side ports.
126 */
127 public static List<String> getLinesidePortId() {
128 return LINESIDE_PORT_ID;
129 }
130
131 /**
132 * add the given flow rules to cross connect-cache.
133 *
134 * @param flowRules flow rules that needs to be cached.
135 */
136 public void setCrossConnectCache(Collection<FlowRule> flowRules) {
137 flowRules.forEach(xc -> crossConnectCache.set(
138 Objects.hash(deviceId, xc.selector(), xc.treatment()),
139 xc.id(),
140 xc.priority()));
141 }
142
143 /**
144 * remove the given flow rules form the cross-connect cache.
145 *
146 * @param flowRules flow rules that needs to be removed from cache.
147 */
148 public void removeCrossConnectCache(Collection<FlowRule> flowRules) {
149 flowRules.forEach(xc -> crossConnectCache.remove(Objects.hash(deviceId, xc.selector(), xc.treatment())));
fahadnaeemkhan71827242017-09-21 15:10:07 -0700150 }
151
152 private final String genPortStateRequest(String state) {
153 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700154 "\"" + STATE + "\": {\n" +
155 "\"" + ADMIN_STATE + "\": \"" + state + "\"\n}\n}";
fahadnaeemkhan71827242017-09-21 15:10:07 -0700156 log.debug("generated request: \n{}", request);
157 return request;
158 }
159
160 private String genWavelengthChangeRequest(String wavelength) {
161 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700162 "\"" + WAVELENGTH + "\": {\n" +
163 "\"" + VALUE + "\": " + wavelength + "\n" +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700164 "}\n" +
165 "}";
166 log.debug("request:\n{}", request);
167 return request;
168
169 }
170
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800171 private String genFrequencyChangeRequest(double frequency) {
fahadnaeemkhan71827242017-09-21 15:10:07 -0700172 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700173 "\"" + FREQUENCY_KEY + "\": {\n" +
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800174 "\"" + VALUE + "\": " + Double.toString(frequency) + "\n" +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700175 "}\n" +
176 "}";
177 log.debug("request:\n{}", request);
178 return request;
179
180 }
181
182 private String genChannelChangeRequest(int channel) {
183 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700184 "\"" + LINE_SYSTEM_CHANNEL_NUMBER + "\": " +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700185 Integer.toString(channel) + "\n}";
186 log.debug("request:\n{}", request);
187 return request;
188
189 }
190
191
192 private final String genUri(String uriFormat, PortNumber port) {
193 return String.format(uriFormat, port.name());
194 }
195
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -0800196 private boolean isPortState(PortNumber number, String state) {
197 log.debug("checking port {} state is {} or not on device {}", number, state, deviceId);
198 String uri = genUri(PORT_STATE_URI, number);
199 JsonNode jsonNode;
200 try {
201 jsonNode = get(uri);
202 } catch (IOException e) {
203 log.error("unable to get port state on device {}", deviceId);
204 return false;
205 }
206 return jsonNode.get(STATE).get(ADMIN_STATE).asText().equals(state);
207
208 }
209
210 private boolean confirmPortState(long timePeriodInMillis, int iterations, PortNumber number, String state) {
211 for (int i = 0; i < iterations; i++) {
212 log.debug("looping for port state with time period {}ms on device {}. try number {}/{}",
213 timePeriodInMillis, deviceId, i + 1, iterations);
214 if (isPortState(number, state)) {
215 return true;
216 }
217 try {
218 Thread.sleep(timePeriodInMillis);
219 } catch (InterruptedException e) {
220 log.error("unable to sleep thread for device {}\n", deviceId, e);
221 Thread.currentThread().interrupt();
222 return false;
223 }
224 }
225 return false;
226 }
227
fahadnaeemkhan71827242017-09-21 15:10:07 -0700228 private boolean changePortState(PortNumber number, String state) {
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700229 log.debug("changing the port {} on device {} state to {}", number, deviceId, state);
fahadnaeemkhan71827242017-09-21 15:10:07 -0700230 String uri = genUri(PORT_STATE_URI, number);
231 String request = genPortStateRequest(state);
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -0800232
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700233 boolean response = putNoReply(uri, request);
234 if (!response) {
235 log.error("unable to change port {} on device {} state to {}", number, deviceId, state);
236 }
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -0800237
238 // 5 tries with 2 sec delay
239 long timePeriod = 2000;
240 int iterations = 5;
241 return confirmPortState(timePeriod, iterations, number, state);
fahadnaeemkhan71827242017-09-21 15:10:07 -0700242 }
243
244 public boolean disablePort(PortNumber number) {
245 return changePortState(number, DISABLED);
246 }
247
248 public boolean enablePort(PortNumber number) {
249 return changePortState(number, ENABLED);
250 }
251
252 public final boolean changeFrequency(OchSignal signal, PortNumber outPort) {
253 String uri = genUri(FREQUENCY_URI, outPort);
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800254 double frequency = signal.centralFrequency().asGHz();
fahadnaeemkhan71827242017-09-21 15:10:07 -0700255 String request = genFrequencyChangeRequest(frequency);
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700256 boolean response = putNoReply(uri, request);
257 if (!response) {
258 log.error("unable to change frequency of port {} on device {}", outPort, deviceId);
259 }
260 return response;
fahadnaeemkhan71827242017-09-21 15:10:07 -0700261 }
262
263 public final boolean changeChannel(OchSignal signal, PortNumber outPort) {
264 String uri = genUri(CHANNEL_URI, outPort);
fahadnaeemkhanffc917f2017-10-03 14:04:46 -0700265 int channel = signal.spacingMultiplier();
fahadnaeemkhan71827242017-09-21 15:10:07 -0700266 log.debug("channel is {} for port {} on device {}", channel, outPort.name(), deviceId);
267 String request = genChannelChangeRequest(channel);
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700268 boolean response = putNoReply(uri, request);
269 if (!response) {
270 log.error("unable to change channel to {} for port {} on device {}",
271 channel, outPort.name(), deviceId);
272 }
273 return response;
fahadnaeemkhan71827242017-09-21 15:10:07 -0700274 }
275
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800276 private int getChannel(PortNumber port) {
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700277 try {
278 String uri = genUri(CHANNEL_URI, port);
279 JsonNode response = get(uri);
280 return response.get(LINE_SYSTEM_CHANNEL_NUMBER).asInt();
281 } catch (IOException e) {
282 // this is expected for client side ports as they don't contain channel data
283 log.error("unable to get channel for port {} on device {}:\n{}", port, deviceId, e);
284 return -1;
285 }
286
287 }
288
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800289 private int getChannelFromFrequency(Frequency frequency) {
fahadnaeemkhanebe2ab72018-02-16 17:42:45 -0800290 return (int) frequency.subtract(Spectrum.CENTER_FREQUENCY)
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800291 .floorDivision(CHANNEL_SPACING.frequency().asHz()).asHz();
292
293 }
294
295 private Frequency getFrequency(PortNumber port) {
296 try {
297 String uri = genUri(FREQUENCY_URI, port);
298 JsonNode response = get(uri);
299 return Frequency.ofGHz(response.get(FREQUENCY_KEY).get(VALUE).asDouble());
300 } catch (IOException e) {
fahadnaeemkhanebe2ab72018-02-16 17:42:45 -0800301 // this is expected for client side ports as they don't contain frequency data
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800302 log.error("unable to get frequency for port {} on device {}:\n{}", port, deviceId, e);
303 return null;
304 }
305
306 }
307
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700308 private AlarmEntityId getAlarmSource(String instance) {
309 AlarmEntityId source;
310 if (instance.contains(PORT)) {
311 source = AlarmEntityId.alarmEntityId(instance.replace("-", ":"));
312 } else if (instance.contains(PTP)) {
313 source = AlarmEntityId.alarmEntityId(instance.replace(PTP + "-", PORT + ":"));
314 } else {
315 source = AlarmEntityId.alarmEntityId(OTHER + ":" + instance);
316 }
317 return source;
318 }
319
320 private long parseAlarmTime(String time) {
321 /*
322 * expecting WaveServer time to be set to UTC.
323 */
324 try {
325 DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
326 LocalDateTime localDateTime = LocalDateTime.parse(time, formatter);
327 return localDateTime.atZone(ZoneId.of(UTC)).toInstant().toEpochMilli();
328 } catch (DateTimeParseException e2) {
329 log.error("unable to parse time {}, using system time", time);
330 return System.currentTimeMillis();
331 }
332 }
333
334 private Alarm newAlarmFromJsonNode(JsonNode jsonNode) {
335 try {
336 AlarmId alarmId = AlarmId.alarmId(checkNotNull(jsonNode.get(ALARM_INSTANCE_ID)).asText());
337 String time = checkNotNull(jsonNode.get(ALARM_LOCAL_DATE_TIME)).asText();
338 String instance = checkNotNull(jsonNode.get(INSTANCE).asText()).toLowerCase();
339 String description = checkNotNull(jsonNode.get(DESCRIPTION)).asText() + " - " + instance + " - " + time;
340 AlarmEntityId source = getAlarmSource(instance);
341 Alarm.SeverityLevel severity = Alarm.SeverityLevel.valueOf(checkNotNull(
342 jsonNode.get(SEVERITY)).asText().toUpperCase());
343
344 long timeRaised = parseAlarmTime(time);
345 boolean isAcknowledged = checkNotNull(jsonNode.get(ACKNOWLEDGE)).asBoolean();
346
347 return new DefaultAlarm.Builder(alarmId, deviceId, description, severity, timeRaised)
348 .withAcknowledged(isAcknowledged)
349 .forSource(source)
350 .build();
351
352 } catch (NullPointerException e) {
353 log.error("got exception while parsing alarm json node {} for device {}:\n", jsonNode, deviceId, e);
354 return null;
355 }
356
357 }
358
359 private List<Alarm> getActiveAlarms() {
360 log.debug("getting active alarms for device {}", deviceId);
361 try {
362 List<JsonNode> alarms = Lists.newArrayList(get(ACTIVE_ALARMS_URL).get(ACTIVE).elements());
363 return alarms.stream()
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800364 .map(this::newAlarmFromJsonNode)
365 .filter(Objects::nonNull)
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700366 .collect(Collectors.toList());
367 } catch (IOException e) {
368 log.error("unable to get active alarms for device {}:\n", deviceId, e);
369 return null;
370 }
371 }
372
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700373 public Collection<FlowEntry> getFlowEntries() {
374 List<Port> ports = deviceService.getPorts(deviceId);
375 //driver only handles lineSide ports
376 //TODO: handle client ports as well
377 return ports.stream()
378 .filter(p -> LINESIDE_PORT_ID.contains(p.number().name()))
379 .map(p -> fetchRule(p.number()))
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800380 .filter(Objects::nonNull)
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700381 .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
382 .collect(Collectors.toList());
383 }
384
385 private FlowRule fetchRule(PortNumber port) {
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800386 Frequency frequency = getFrequency(port);
387 if (frequency == null) {
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700388 return null;
389 }
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800390 int channel = getChannelFromFrequency(frequency);
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700391 /*
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700392 * both inPort and outPort will be same as WaveServer only deal with same port ptp-indexes
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700393 * channel and spaceMultiplier are same.
394 * TODO: find a way to get both inPort and outPort for future when inPort may not be equal to outPort
395 */
396
397 TrafficSelector selector = DefaultTrafficSelector.builder()
398 .matchInPort(port)
399 .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800400 .add(Criteria.matchLambda(OchSignal.newDwdmSlot(CHANNEL_SPACING, channel)))
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700401 .build();
402 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
403 .setOutput(port)
404 .build();
405
406 int hash = Objects.hash(deviceId, selector, treatment);
407 Pair<FlowId, Integer> lookup = crossConnectCache.get(hash);
408 if (lookup == null) {
409 return null;
410 }
411
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800412 return DefaultFlowRule.builder()
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700413 .forDevice(deviceId)
414 .makePermanent()
415 .withSelector(selector)
416 .withTreatment(treatment)
417 .withPriority(lookup.getRight())
418 .withCookie(lookup.getLeft().value())
419 .build();
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700420 }
421
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700422 public List<Alarm> getAlarms() {
423 log.debug("getting alarms for device {}", deviceId);
424 return getActiveAlarms();
425 }
426
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700427 private JsonNode get(String uri) throws IOException {
428 InputStream response = controller.get(deviceId, uri, MediaType.valueOf(MediaType.APPLICATION_JSON));
429 ObjectMapper om = new ObjectMapper();
430 final ObjectReader reader = om.reader();
431 // all waveserver responses contain data node, which contains the requested data
432 return reader.readTree(response).get(DATA);
433 }
434
fahadnaeemkhan71827242017-09-21 15:10:07 -0700435 private int put(String uri, String request) {
436 InputStream payload = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8));
437 int response = controller.put(deviceId, uri, payload, MediaType.valueOf(MediaType.APPLICATION_JSON));
438 log.debug("response: {}", response);
439 return response;
440 }
441
442 private boolean putNoReply(String uri, String request) {
443 return put(uri, request) == Response.Status.NO_CONTENT.getStatusCode();
444 }
445
446}