blob: 260a709a984bf77addbe9a3c0b6fa5467ec3ee6d [file] [log] [blame]
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -08003 *
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 */
16package org.onosproject.incubator.rpc.grpc;
17
18import static com.google.common.base.Preconditions.checkNotNull;
Yuta HIGUCHI4c7c90a2016-07-06 14:56:49 -070019import static java.util.concurrent.Executors.newScheduledThreadPool;
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -080020import static java.util.stream.Collectors.toList;
HIGUCHI Yuta06c1a3f2016-05-23 12:54:55 -070021import static org.onosproject.incubator.protobuf.net.ProtobufUtils.translate;
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -080022import static org.onosproject.net.DeviceId.deviceId;
23
24import java.io.IOException;
HIGUCHI Yuta7c1583c2015-12-03 23:08:54 -080025import java.util.Map;
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -080026import java.util.Set;
27import java.util.concurrent.CompletableFuture;
28import java.util.concurrent.ExecutionException;
Yuta HIGUCHI4c7c90a2016-07-06 14:56:49 -070029import java.util.concurrent.ScheduledExecutorService;
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -080030import java.util.concurrent.TimeUnit;
31import java.util.concurrent.TimeoutException;
32import java.util.concurrent.atomic.AtomicInteger;
33
34import org.apache.felix.scr.annotations.Activate;
35import org.apache.felix.scr.annotations.Component;
36import org.apache.felix.scr.annotations.Deactivate;
37import org.apache.felix.scr.annotations.Modified;
38import org.apache.felix.scr.annotations.Property;
39import org.apache.felix.scr.annotations.Reference;
40import org.apache.felix.scr.annotations.ReferenceCardinality;
Yuta HIGUCHI4c7c90a2016-07-06 14:56:49 -070041import org.onlab.util.Tools;
HIGUCHI Yutae3e90632016-05-11 16:44:01 -070042import org.onosproject.grpc.net.device.DeviceProviderRegistryRpcGrpc;
43import org.onosproject.grpc.net.device.DeviceProviderRegistryRpcGrpc.DeviceProviderRegistryRpc;
44import org.onosproject.grpc.net.device.DeviceService.DeviceConnected;
45import org.onosproject.grpc.net.device.DeviceService.DeviceDisconnected;
46import org.onosproject.grpc.net.device.DeviceService.DeviceProviderMsg;
47import org.onosproject.grpc.net.device.DeviceService.DeviceProviderServiceMsg;
48import org.onosproject.grpc.net.device.DeviceService.IsReachableResponse;
49import org.onosproject.grpc.net.device.DeviceService.PortStatusChanged;
50import org.onosproject.grpc.net.device.DeviceService.ReceivedRoleReply;
51import org.onosproject.grpc.net.device.DeviceService.RegisterProvider;
52import org.onosproject.grpc.net.device.DeviceService.UpdatePortStatistics;
53import org.onosproject.grpc.net.device.DeviceService.UpdatePorts;
54import org.onosproject.grpc.net.link.LinkProviderServiceRpcGrpc;
HIGUCHI Yuta06c1a3f2016-05-23 12:54:55 -070055import org.onosproject.incubator.protobuf.net.ProtobufUtils;
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -080056import org.onosproject.net.DeviceId;
57import org.onosproject.net.MastershipRole;
Saurav Dasa2d37502016-03-25 17:50:40 -070058import org.onosproject.net.PortNumber;
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -080059import org.onosproject.net.device.DeviceProvider;
60import org.onosproject.net.device.DeviceProviderRegistry;
61import org.onosproject.net.device.DeviceProviderService;
HIGUCHI Yuta7c1583c2015-12-03 23:08:54 -080062import org.onosproject.net.link.LinkProvider;
63import org.onosproject.net.link.LinkProviderRegistry;
64import org.onosproject.net.link.LinkProviderService;
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -080065import org.onosproject.net.provider.ProviderId;
66import org.osgi.service.component.ComponentContext;
67import org.slf4j.Logger;
68import org.slf4j.LoggerFactory;
69
70import com.google.common.cache.Cache;
71import com.google.common.cache.CacheBuilder;
HIGUCHI Yuta7c1583c2015-12-03 23:08:54 -080072import com.google.common.collect.Maps;
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -080073import com.google.common.collect.Sets;
74
75import io.grpc.Server;
76import io.grpc.netty.NettyServerBuilder;
77import io.grpc.stub.StreamObserver;
78
79// gRPC Server on Metro-side
80// Translates request received on RPC channel, and calls corresponding Service on
81// Metro-ONOS cluster.
HIGUCHI Yuta7c1583c2015-12-03 23:08:54 -080082
83// Currently supports DeviceProviderRegistry, LinkProviderService
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -080084/**
85 * Server side implementation of gRPC based RemoteService.
86 */
87@Component(immediate = true)
88public class GrpcRemoteServiceServer {
89
HIGUCHI Yuta7c1583c2015-12-03 23:08:54 -080090 static final String RPC_PROVIDER_NAME = "org.onosproject.rpc.provider.grpc";
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -080091
92 // TODO pick a number
93 public static final int DEFAULT_LISTEN_PORT = 11984;
94
95 private final Logger log = LoggerFactory.getLogger(getClass());
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected DeviceProviderRegistry deviceProviderRegistry;
99
HIGUCHI Yuta7c1583c2015-12-03 23:08:54 -0800100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected LinkProviderRegistry linkProviderRegistry;
102
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800103
104 @Property(name = "listenPort", intValue = DEFAULT_LISTEN_PORT,
105 label = "Port to listen on")
106 protected int listenPort = DEFAULT_LISTEN_PORT;
107
108 private Server server;
109 private final Set<DeviceProviderServerProxy> registeredProviders = Sets.newConcurrentHashSet();
110
HIGUCHI Yuta7c1583c2015-12-03 23:08:54 -0800111 // scheme -> ...
112 // updates must be guarded by synchronizing `this`
113 private final Map<String, LinkProviderService> linkProviderServices = Maps.newConcurrentMap();
114 private final Map<String, LinkProvider> linkProviders = Maps.newConcurrentMap();
115
Yuta HIGUCHI4c7c90a2016-07-06 14:56:49 -0700116 private ScheduledExecutorService executor;
117
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800118 @Activate
119 protected void activate(ComponentContext context) throws IOException {
Yuta HIGUCHI4c7c90a2016-07-06 14:56:49 -0700120 executor = newScheduledThreadPool(1, Tools.groupedThreads("grpc", "%d", log));
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800121 modified(context);
122
123 log.debug("Server starting on {}", listenPort);
124 try {
125 server = NettyServerBuilder.forPort(listenPort)
126 .addService(DeviceProviderRegistryRpcGrpc.bindService(new DeviceProviderRegistryServerProxy()))
HIGUCHI Yuta7c1583c2015-12-03 23:08:54 -0800127 .addService(LinkProviderServiceRpcGrpc.bindService(new LinkProviderServiceServerProxy(this)))
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800128 .build().start();
129 } catch (IOException e) {
130 log.error("Failed to start gRPC server", e);
131 throw e;
132 }
133
134 log.info("Started on {}", listenPort);
135 }
136
137 @Deactivate
138 protected void deactivate() {
Yuta HIGUCHI4c7c90a2016-07-06 14:56:49 -0700139 executor.shutdown();
140 try {
141 executor.awaitTermination(5, TimeUnit.SECONDS);
142 } catch (InterruptedException e) {
143 Thread.currentThread().interrupt();
144 }
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800145
Sho SHIMIZUa09e1bb2016-08-01 14:25:25 -0700146 registeredProviders.forEach(deviceProviderRegistry::unregister);
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800147
148 server.shutdown();
149 // Should we wait for shutdown?
HIGUCHI Yuta7c1583c2015-12-03 23:08:54 -0800150
151 unregisterLinkProviders();
152
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800153 log.info("Stopped");
154 }
155
156 @Modified
157 public void modified(ComponentContext context) {
158 // TODO support dynamic reconfiguration and restarting server?
159 }
160
HIGUCHI Yuta7c1583c2015-12-03 23:08:54 -0800161 /**
162 * Registers {@link StubLinkProvider} for given ProviderId scheme.
163 *
164 * DO NOT DIRECTLY CALL THIS METHOD.
165 * Only expected to be called from {@link #getLinkProviderServiceFor(String)}.
166 *
167 * @param scheme ProviderId scheme.
168 * @return {@link LinkProviderService} registered.
169 */
170 private synchronized LinkProviderService registerStubLinkProvider(String scheme) {
171 StubLinkProvider provider = new StubLinkProvider(scheme);
172 linkProviders.put(scheme, provider);
173 return linkProviderRegistry.register(provider);
174 }
175
176 /**
177 * Unregisters all registered LinkProviders.
178 */
179 private synchronized void unregisterLinkProviders() {
HIGUCHI Yuta6381a242016-03-13 23:29:10 -0700180 // TODO remove all links registered by these providers
HIGUCHI Yuta7c1583c2015-12-03 23:08:54 -0800181 linkProviders.values().forEach(linkProviderRegistry::unregister);
182 linkProviders.clear();
183 linkProviderServices.clear();
184 }
185
186 /**
187 * Gets or creates {@link LinkProviderService} registered for given ProviderId scheme.
188 *
189 * @param scheme ProviderId scheme.
190 * @return {@link LinkProviderService}
191 */
192 protected LinkProviderService getLinkProviderServiceFor(String scheme) {
193 return linkProviderServices.computeIfAbsent(scheme, this::registerStubLinkProvider);
194 }
195
Yuta HIGUCHI4c7c90a2016-07-06 14:56:49 -0700196 protected ScheduledExecutorService getSharedExecutor() {
197 return executor;
198 }
199
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800200 // RPC Server-side code
201 // RPC session Factory
202 /**
203 * Relays DeviceProviderRegistry calls from RPC client.
204 */
205 class DeviceProviderRegistryServerProxy implements DeviceProviderRegistryRpc {
206
207 @Override
208 public StreamObserver<DeviceProviderServiceMsg> register(StreamObserver<DeviceProviderMsg> toDeviceProvider) {
209 log.trace("DeviceProviderRegistryServerProxy#register called!");
210
211 DeviceProviderServerProxy provider = new DeviceProviderServerProxy(toDeviceProvider);
212
213 return new DeviceProviderServiceServerProxy(provider, toDeviceProvider);
214 }
215 }
216
217 // Lower -> Upper Controller message
218 // RPC Server-side code
219 // RPC session handler
220 private final class DeviceProviderServiceServerProxy
221 implements StreamObserver<DeviceProviderServiceMsg> {
222
223 // intentionally shadowing
224 private final Logger log = LoggerFactory.getLogger(getClass());
225
226 private final DeviceProviderServerProxy pairedProvider;
227 private final StreamObserver<DeviceProviderMsg> toDeviceProvider;
228
229 private final Cache<Integer, CompletableFuture<Boolean>> outstandingIsReachable;
230
231 // wrapped providerService
232 private DeviceProviderService deviceProviderService;
233
234
235 DeviceProviderServiceServerProxy(DeviceProviderServerProxy provider,
236 StreamObserver<DeviceProviderMsg> toDeviceProvider) {
237 this.pairedProvider = provider;
238 this.toDeviceProvider = toDeviceProvider;
239 outstandingIsReachable = CacheBuilder.newBuilder()
240 .expireAfterWrite(1, TimeUnit.MINUTES)
241 .build();
242
243 // pair RPC session in other direction
244 provider.pair(this);
245 }
246
247 @Override
248 public void onNext(DeviceProviderServiceMsg msg) {
249 try {
250 log.trace("DeviceProviderServiceServerProxy received: {}", msg);
251 onMethod(msg);
252 } catch (Exception e) {
253 log.error("Exception thrown handling {}", msg, e);
254 onError(e);
255 throw e;
256 }
257 }
258
259 /**
260 * Translates received RPC message to {@link DeviceProviderService} method calls.
261 * @param msg DeviceProviderService message
262 */
263 private void onMethod(DeviceProviderServiceMsg msg) {
264 switch (msg.getMethodCase()) {
265 case REGISTER_PROVIDER:
266 RegisterProvider registerProvider = msg.getRegisterProvider();
267 // TODO Do we care about provider name?
268 pairedProvider.setProviderId(new ProviderId(registerProvider.getProviderScheme(), RPC_PROVIDER_NAME));
269 registeredProviders.add(pairedProvider);
HIGUCHI Yuta6381a242016-03-13 23:29:10 -0700270 log.info("registering DeviceProvider {} via gRPC", pairedProvider.id());
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800271 deviceProviderService = deviceProviderRegistry.register(pairedProvider);
272 break;
273
274 case DEVICE_CONNECTED:
275 DeviceConnected deviceConnected = msg.getDeviceConnected();
276 deviceProviderService.deviceConnected(deviceId(deviceConnected.getDeviceId()),
277 translate(deviceConnected.getDeviceDescription()));
278 break;
279 case DEVICE_DISCONNECTED:
280 DeviceDisconnected deviceDisconnected = msg.getDeviceDisconnected();
281 deviceProviderService.deviceDisconnected(deviceId(deviceDisconnected.getDeviceId()));
282 break;
283 case UPDATE_PORTS:
284 UpdatePorts updatePorts = msg.getUpdatePorts();
285 deviceProviderService.updatePorts(deviceId(updatePorts.getDeviceId()),
286 updatePorts.getPortDescriptionsList()
287 .stream()
HIGUCHI Yuta06c1a3f2016-05-23 12:54:55 -0700288 .map(ProtobufUtils::translate)
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800289 .collect(toList()));
290 break;
291 case PORT_STATUS_CHANGED:
292 PortStatusChanged portStatusChanged = msg.getPortStatusChanged();
293 deviceProviderService.portStatusChanged(deviceId(portStatusChanged.getDeviceId()),
294 translate(portStatusChanged.getPortDescription()));
295 break;
296 case RECEIVED_ROLE_REPLY:
297 ReceivedRoleReply receivedRoleReply = msg.getReceivedRoleReply();
298 deviceProviderService.receivedRoleReply(deviceId(receivedRoleReply.getDeviceId()),
299 translate(receivedRoleReply.getRequested()),
300 translate(receivedRoleReply.getResponse()));
301 break;
302 case UPDATE_PORT_STATISTICS:
303 UpdatePortStatistics updatePortStatistics = msg.getUpdatePortStatistics();
304 deviceProviderService.updatePortStatistics(deviceId(updatePortStatistics.getDeviceId()),
305 updatePortStatistics.getPortStatisticsList()
306 .stream()
HIGUCHI Yuta06c1a3f2016-05-23 12:54:55 -0700307 .map(ProtobufUtils::translate)
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800308 .collect(toList()));
309 break;
310
311 // return value of DeviceProvider#isReachable
312 case IS_REACHABLE_RESPONSE:
313 IsReachableResponse isReachableResponse = msg.getIsReachableResponse();
314 int xid = isReachableResponse.getXid();
315 boolean isReachable = isReachableResponse.getIsReachable();
316 CompletableFuture<Boolean> result = outstandingIsReachable.asMap().remove(xid);
317 if (result != null) {
318 result.complete(isReachable);
319 }
320 break;
321
322 case METHOD_NOT_SET:
323 default:
324 log.warn("Unexpected message received {}", msg);
325 break;
326 }
327 }
328
329 @Override
330 public void onCompleted() {
331 log.info("DeviceProviderServiceServerProxy completed");
332 deviceProviderRegistry.unregister(pairedProvider);
333 registeredProviders.remove(pairedProvider);
334 toDeviceProvider.onCompleted();
335 }
336
337 @Override
338 public void onError(Throwable e) {
339 log.error("DeviceProviderServiceServerProxy#onError", e);
HIGUCHI Yuta6381a242016-03-13 23:29:10 -0700340 if (pairedProvider != null) {
341 // TODO call deviceDisconnected against all devices
342 // registered for this provider scheme
343 log.info("unregistering DeviceProvider {} via gRPC", pairedProvider.id());
344 deviceProviderRegistry.unregister(pairedProvider);
345 registeredProviders.remove(pairedProvider);
346 }
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800347 // TODO What is the proper clean up for bi-di stream on error?
348 // sample suggests no-op
349 toDeviceProvider.onError(e);
350 }
351
352
353 /**
354 * Registers Future for {@link DeviceProvider#isReachable(DeviceId)} return value.
355 * @param xid IsReachable call ID.
356 * @param reply Future to
357 */
358 void register(int xid, CompletableFuture<Boolean> reply) {
359 outstandingIsReachable.put(xid, reply);
360 }
361
362 }
363
364 // Upper -> Lower Controller message
365 /**
366 * Relay DeviceProvider calls to RPC client.
367 */
368 private final class DeviceProviderServerProxy
369 implements DeviceProvider {
370
371 private final Logger log = LoggerFactory.getLogger(getClass());
372
373 // xid for isReachable calls
374 private final AtomicInteger xidPool = new AtomicInteger();
375 private final StreamObserver<DeviceProviderMsg> toDeviceProvider;
376
377 private DeviceProviderServiceServerProxy deviceProviderServiceProxy = null;
378 private ProviderId providerId;
379
380 DeviceProviderServerProxy(StreamObserver<DeviceProviderMsg> toDeviceProvider) {
381 this.toDeviceProvider = toDeviceProvider;
382 }
383
384 void setProviderId(ProviderId pid) {
385 this.providerId = pid;
386 }
387
388 /**
389 * Registers RPC stream in other direction.
HIGUCHI Yuta6381a242016-03-13 23:29:10 -0700390 *
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800391 * @param deviceProviderServiceProxy {@link DeviceProviderServiceServerProxy}
392 */
393 void pair(DeviceProviderServiceServerProxy deviceProviderServiceProxy) {
394 this.deviceProviderServiceProxy = deviceProviderServiceProxy;
395 }
396
397 @Override
398 public void triggerProbe(DeviceId deviceId) {
Yuta HIGUCHIad4861e2016-07-13 19:19:03 -0700399 try {
400 onTriggerProbe(deviceId);
401 } catch (Exception e) {
402 log.error("Exception caught handling triggerProbe({})",
403 deviceId, e);
404 toDeviceProvider.onError(e);
405 }
406 }
407
408 private void onTriggerProbe(DeviceId deviceId) {
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800409 log.trace("triggerProbe({})", deviceId);
410 DeviceProviderMsg.Builder msgBuilder = DeviceProviderMsg.newBuilder();
411 msgBuilder.setTriggerProbe(msgBuilder.getTriggerProbeBuilder()
412 .setDeviceId(deviceId.toString())
413 .build());
414 DeviceProviderMsg triggerProbeMsg = msgBuilder.build();
415 toDeviceProvider.onNext(triggerProbeMsg);
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800416 }
417
418 @Override
419 public void roleChanged(DeviceId deviceId, MastershipRole newRole) {
Yuta HIGUCHIad4861e2016-07-13 19:19:03 -0700420 try {
421 onRoleChanged(deviceId, newRole);
422 } catch (Exception e) {
423 log.error("Exception caught handling onRoleChanged({}, {})",
424 deviceId, newRole, e);
425 toDeviceProvider.onError(e);
426 }
427 }
428
429 private void onRoleChanged(DeviceId deviceId, MastershipRole newRole) {
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800430 log.trace("roleChanged({}, {})", deviceId, newRole);
431 DeviceProviderMsg.Builder msgBuilder = DeviceProviderMsg.newBuilder();
432 msgBuilder.setRoleChanged(msgBuilder.getRoleChangedBuilder()
433 .setDeviceId(deviceId.toString())
434 .setNewRole(translate(newRole))
435 .build());
436 toDeviceProvider.onNext(msgBuilder.build());
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800437 }
438
439 @Override
440 public boolean isReachable(DeviceId deviceId) {
Yuta HIGUCHIad4861e2016-07-13 19:19:03 -0700441 try {
442 return onIsReachable(deviceId);
443 } catch (Exception e) {
444 log.error("Exception caught handling onIsReachable({})",
445 deviceId, e);
446 toDeviceProvider.onError(e);
447 return false;
448 }
449 }
450
451 private boolean onIsReachable(DeviceId deviceId) {
452
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800453 log.trace("isReachable({})", deviceId);
454 CompletableFuture<Boolean> result = new CompletableFuture<>();
455 final int xid = xidPool.incrementAndGet();
456
457 DeviceProviderMsg.Builder msgBuilder = DeviceProviderMsg.newBuilder();
458 msgBuilder.setIsReachableRequest(msgBuilder.getIsReachableRequestBuilder()
459 .setXid(xid)
460 .setDeviceId(deviceId.toString())
461 .build());
462
463 // Associate xid and register above future some where
464 // in DeviceProviderService channel to receive reply
465 if (deviceProviderServiceProxy != null) {
466 deviceProviderServiceProxy.register(xid, result);
467 }
468
469 // send message down RPC
470 toDeviceProvider.onNext(msgBuilder.build());
471
472 // wait for reply
473 try {
474 return result.get(10, TimeUnit.SECONDS);
475 } catch (InterruptedException e) {
476 log.debug("isReachable({}) was Interrupted", deviceId, e);
477 Thread.currentThread().interrupt();
478 } catch (TimeoutException e) {
479 log.warn("isReachable({}) Timed out", deviceId, e);
480 } catch (ExecutionException e) {
481 log.error("isReachable({}) Execution failed", deviceId, e);
Yuta HIGUCHIad4861e2016-07-13 19:19:03 -0700482 // close session
483 toDeviceProvider.onError(e);
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800484 }
485 return false;
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800486 }
487
488 @Override
489 public ProviderId id() {
490 return checkNotNull(providerId, "not initialized yet");
491 }
492
Saurav Dasa2d37502016-03-25 17:50:40 -0700493 @Override
494 public void changePortState(DeviceId deviceId, PortNumber portNumber,
495 boolean enable) {
Yuta HIGUCHIad4861e2016-07-13 19:19:03 -0700496 // TODO Implement if required
497 log.error("changePortState not supported yet");
498 toDeviceProvider.onError(new UnsupportedOperationException("not implemented yet"));
Saurav Dasa2d37502016-03-25 17:50:40 -0700499 }
500
HIGUCHI Yuta15653fd2015-11-09 11:05:09 -0800501 }
502}