2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.sample.toaster.provider;
10 import java.util.Arrays;
11 import java.util.Collections;
12 import java.util.List;
13 import java.util.concurrent.Callable;
14 import java.util.concurrent.ExecutionException;
15 import java.util.concurrent.ExecutorService;
16 import java.util.concurrent.Executors;
17 import java.util.concurrent.Future;
18 import java.util.concurrent.atomic.AtomicLong;
19 import java.util.concurrent.atomic.AtomicReference;
21 import org.opendaylight.controller.config.yang.config.toaster_provider.impl.ToasterProviderRuntimeMXBean;
22 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
23 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
24 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
25 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
26 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
27 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
28 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
29 import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
30 import org.opendaylight.controller.sal.binding.api.NotificationProviderService;
31 import org.opendaylight.controller.sal.common.util.RpcErrors;
32 import org.opendaylight.controller.sal.common.util.Rpcs;
33 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.DisplayString;
34 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastInput;
35 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.RestockToasterInput;
36 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.Toaster;
37 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.Toaster.ToasterStatus;
38 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterBuilder;
39 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterOutOfBreadBuilder;
40 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestocked;
41 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestockedBuilder;
42 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterService;
43 import org.opendaylight.yangtools.yang.binding.DataObject;
44 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
45 import org.opendaylight.yangtools.yang.common.RpcError;
46 import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity;
47 import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
48 import org.opendaylight.yangtools.yang.common.RpcResult;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
52 import com.google.common.base.Function;
53 import com.google.common.base.Optional;
54 import com.google.common.util.concurrent.AsyncFunction;
55 import com.google.common.util.concurrent.FutureCallback;
56 import com.google.common.util.concurrent.Futures;
57 import com.google.common.util.concurrent.ListenableFuture;
58 import com.google.common.util.concurrent.SettableFuture;
60 public class OpendaylightToaster implements ToasterService, ToasterProviderRuntimeMXBean,
61 DataChangeListener, AutoCloseable {
63 private static final Logger LOG = LoggerFactory.getLogger(OpendaylightToaster.class);
65 public static final InstanceIdentifier<Toaster> TOASTER_IID = InstanceIdentifier.builder(Toaster.class).build();
67 private static final DisplayString TOASTER_MANUFACTURER = new DisplayString("Opendaylight");
68 private static final DisplayString TOASTER_MODEL_NUMBER = new DisplayString("Model 1 - Binding Aware");
70 private NotificationProviderService notificationProvider;
71 private DataBroker dataProvider;
73 private final ExecutorService executor;
75 // The following holds the Future for the current make toast task.
76 // This is used to cancel the current toast.
77 private final AtomicReference<Future<?>> currentMakeToastTask = new AtomicReference<>();
79 private final AtomicLong amountOfBreadInStock = new AtomicLong( 100 );
81 private final AtomicLong toastsMade = new AtomicLong(0);
83 // Thread safe holder for our darkness multiplier.
84 private final AtomicLong darknessFactor = new AtomicLong( 1000 );
86 public OpendaylightToaster() {
87 executor = Executors.newFixedThreadPool(1);
90 public void setNotificationProvider(final NotificationProviderService salService) {
91 this.notificationProvider = salService;
94 public void setDataProvider(final DataBroker salDataProvider) {
95 this.dataProvider = salDataProvider;
96 setToasterStatusUp( null );
100 * Implemented from the AutoCloseable interface.
103 public void close() throws ExecutionException, InterruptedException {
104 // When we close this service we need to shutdown our executor!
107 if (dataProvider != null) {
108 WriteTransaction t = dataProvider.newWriteOnlyTransaction();
109 t.delete(LogicalDatastoreType.OPERATIONAL,TOASTER_IID);
110 ListenableFuture<RpcResult<TransactionStatus>> future = t.commit();
111 Futures.addCallback( future, new FutureCallback<RpcResult<TransactionStatus>>() {
113 public void onSuccess( RpcResult<TransactionStatus> result ) {
114 LOG.debug( "Delete Toaster commit result: " + result );
118 public void onFailure( Throwable t ) {
119 LOG.error( "Delete of Toaster failed", t );
125 private Toaster buildToaster( ToasterStatus status ) {
127 // note - we are simulating a device whose manufacture and model are
128 // fixed (embedded) into the hardware.
129 // This is why the manufacture and model number are hardcoded.
130 return new ToasterBuilder().setToasterManufacturer( TOASTER_MANUFACTURER )
131 .setToasterModelNumber( TOASTER_MODEL_NUMBER )
132 .setToasterStatus( status )
137 * Implemented from the DataChangeListener interface.
140 public void onDataChanged( final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change ) {
141 DataObject dataObject = change.getUpdatedSubtree();
142 if( dataObject instanceof Toaster )
144 Toaster toaster = (Toaster) dataObject;
145 Long darkness = toaster.getDarknessFactor();
146 if( darkness != null )
148 darknessFactor.set( darkness );
154 * RPC call implemented from the ToasterService interface that cancels the current
158 public Future<RpcResult<Void>> cancelToast() {
160 Future<?> current = currentMakeToastTask.getAndSet( null );
161 if( current != null ) {
162 current.cancel( true );
165 // Always return success from the cancel toast call.
166 return Futures.immediateFuture( Rpcs.<Void> getRpcResult( true,
167 Collections.<RpcError>emptyList() ) );
171 * RPC call implemented from the ToasterService interface that attempts to make toast.
174 public Future<RpcResult<Void>> makeToast(final MakeToastInput input) {
175 LOG.info("makeToast: " + input);
177 final SettableFuture<RpcResult<Void>> futureResult = SettableFuture.create();
179 checkStatusAndMakeToast( input, futureResult );
184 private List<RpcError> makeToasterOutOfBreadError() {
185 return Arrays.asList(
186 RpcErrors.getRpcError( "out-of-stock", "resource-denied", null, null,
187 "Toaster is out of bread",
188 ErrorType.APPLICATION, null ) );
191 private List<RpcError> makeToasterInUseError() {
192 return Arrays.asList(
193 RpcErrors.getRpcError( "", "in-use", null, ErrorSeverity.WARNING,
194 "Toaster is busy", ErrorType.APPLICATION, null ) );
197 private void checkStatusAndMakeToast( final MakeToastInput input,
198 final SettableFuture<RpcResult<Void>> futureResult ) {
200 // Read the ToasterStatus and, if currently Up, try to write the status to Down.
201 // If that succeeds, then we essentially have an exclusive lock and can proceed
204 final ReadWriteTransaction tx = dataProvider.newReadWriteTransaction();
205 ListenableFuture<Optional<DataObject>> readFuture =
206 tx.read( LogicalDatastoreType.OPERATIONAL, TOASTER_IID );
208 final ListenableFuture<RpcResult<TransactionStatus>> commitFuture =
209 Futures.transform( readFuture, new AsyncFunction<Optional<DataObject>,
210 RpcResult<TransactionStatus>>() {
213 public ListenableFuture<RpcResult<TransactionStatus>> apply(
214 Optional<DataObject> toasterData ) throws Exception {
216 ToasterStatus toasterStatus = ToasterStatus.Up;
217 if( toasterData.isPresent() ) {
218 toasterStatus = ((Toaster)toasterData.get()).getToasterStatus();
221 LOG.debug( "Read toaster status: {}", toasterStatus );
223 if( toasterStatus == ToasterStatus.Up ) {
226 LOG.debug( "Toaster is out of bread" );
228 return Futures.immediateFuture( Rpcs.<TransactionStatus>getRpcResult(
229 false, null, makeToasterOutOfBreadError() ) );
232 LOG.debug( "Setting Toaster status to Down" );
234 // We're not currently making toast - try to update the status to Down
235 // to indicate we're going to make toast. This acts as a lock to prevent
236 // concurrent toasting.
237 tx.put( LogicalDatastoreType.OPERATIONAL, TOASTER_IID,
238 buildToaster( ToasterStatus.Down ) );
242 LOG.debug( "Oops - already making toast!" );
244 // Return an error since we are already making toast. This will get
245 // propagated to the commitFuture below which will interpret the null
246 // TransactionStatus in the RpcResult as an error condition.
247 return Futures.immediateFuture( Rpcs.<TransactionStatus>getRpcResult(
248 false, null, makeToasterInUseError() ) );
252 Futures.addCallback( commitFuture, new FutureCallback<RpcResult<TransactionStatus>>() {
254 public void onSuccess( RpcResult<TransactionStatus> result ) {
255 if( result.getResult() == TransactionStatus.COMMITED ) {
258 currentMakeToastTask.set( executor.submit(
259 new MakeToastTask( input, futureResult ) ) );
262 LOG.debug( "Setting error result" );
264 // Either the transaction failed to commit for some reason or, more likely,
265 // the read above returned ToasterStatus.Down. Either way, fail the
266 // futureResult and copy the errors.
268 futureResult.set( Rpcs.<Void>getRpcResult( false, null, result.getErrors() ) );
273 public void onFailure( Throwable ex ) {
274 if( ex instanceof OptimisticLockFailedException ) {
276 // Another thread is likely trying to make toast simultaneously and updated the
277 // status before us. Try reading the status again - if another make toast is
278 // now in progress, we should get ToasterStatus.Down and fail.
280 LOG.debug( "Got OptimisticLockFailedException - trying again" );
282 checkStatusAndMakeToast( input, futureResult );
286 LOG.error( "Failed to commit Toaster status", ex );
288 // Got some unexpected error so fail.
289 futureResult.set( Rpcs.<Void> getRpcResult( false, null, Arrays.asList(
290 RpcErrors.getRpcError( null, null, null, ErrorSeverity.ERROR,
292 ErrorType.APPLICATION, ex ) ) ) );
299 * RestConf RPC call implemented from the ToasterService interface.
300 * Restocks the bread for the toaster, resets the toastsMade counter to 0, and sends a
301 * ToasterRestocked notification.
304 public Future<RpcResult<java.lang.Void>> restockToaster(final RestockToasterInput input) {
305 LOG.info( "restockToaster: " + input );
307 amountOfBreadInStock.set( input.getAmountOfBreadToStock() );
309 if( amountOfBreadInStock.get() > 0 ) {
310 ToasterRestocked reStockedNotification = new ToasterRestockedBuilder()
311 .setAmountOfBread( input.getAmountOfBreadToStock() ).build();
312 notificationProvider.publish( reStockedNotification );
315 return Futures.immediateFuture(Rpcs.<Void> getRpcResult(true, Collections.<RpcError>emptyList()));
319 * JMX RPC call implemented from the ToasterProviderRuntimeMXBean interface.
322 public void clearToastsMade() {
323 LOG.info( "clearToastsMade" );
328 * Accesssor method implemented from the ToasterProviderRuntimeMXBean interface.
331 public Long getToastsMade() {
332 return toastsMade.get();
335 private void setToasterStatusUp( final Function<Boolean,Void> resultCallback ) {
337 WriteTransaction tx = dataProvider.newWriteOnlyTransaction();
338 tx.put( LogicalDatastoreType.OPERATIONAL,TOASTER_IID, buildToaster( ToasterStatus.Up ) );
340 ListenableFuture<RpcResult<TransactionStatus>> commitFuture = tx.commit();
342 Futures.addCallback( commitFuture, new FutureCallback<RpcResult<TransactionStatus>>() {
344 public void onSuccess( RpcResult<TransactionStatus> result ) {
345 if( result.getResult() != TransactionStatus.COMMITED ) {
346 LOG.error( "Failed to update toaster status: " + result.getErrors() );
349 notifyCallback( result.getResult() == TransactionStatus.COMMITED );
353 public void onFailure( Throwable t ) {
354 // We shouldn't get an OptimisticLockFailedException (or any ex) as no
355 // other component should be updating the operational state.
356 LOG.error( "Failed to update toaster status", t );
358 notifyCallback( false );
361 void notifyCallback( boolean result ) {
362 if( resultCallback != null ) {
363 resultCallback.apply( result );
369 private boolean outOfBread()
371 return amountOfBreadInStock.get() == 0;
374 private class MakeToastTask implements Callable<Void> {
376 final MakeToastInput toastRequest;
377 final SettableFuture<RpcResult<Void>> futureResult;
379 public MakeToastTask( final MakeToastInput toastRequest,
380 final SettableFuture<RpcResult<Void>> futureResult ) {
381 this.toastRequest = toastRequest;
382 this.futureResult = futureResult;
389 // make toast just sleeps for n seconds per doneness level.
390 long darknessFactor = OpendaylightToaster.this.darknessFactor.get();
391 Thread.sleep(darknessFactor * toastRequest.getToasterDoneness());
394 catch( InterruptedException e ) {
395 LOG.info( "Interrupted while making the toast" );
398 toastsMade.incrementAndGet();
400 amountOfBreadInStock.getAndDecrement();
402 LOG.info( "Toaster is out of bread!" );
404 notificationProvider.publish( new ToasterOutOfBreadBuilder().build() );
407 // Set the Toaster status back to up - this essentially releases the toasting lock.
408 // We can't clear the current toast task nor set the Future result until the
409 // update has been committed so we pass a callback to be notified on completion.
411 setToasterStatusUp( new Function<Boolean,Void>() {
413 public Void apply( Boolean result ) {
415 currentMakeToastTask.set( null );
417 LOG.debug("Toast done");
419 futureResult.set( Rpcs.<Void>getRpcResult( true, null,
420 Collections.<RpcError>emptyList() ) );