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 com.google.common.base.Function;
11 import com.google.common.base.Optional;
12 import com.google.common.util.concurrent.AsyncFunction;
13 import com.google.common.util.concurrent.FutureCallback;
14 import com.google.common.util.concurrent.Futures;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import com.google.common.util.concurrent.SettableFuture;
17 import java.util.Collection;
18 import java.util.concurrent.Callable;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.ExecutorService;
21 import java.util.concurrent.Executors;
22 import java.util.concurrent.Future;
23 import java.util.concurrent.atomic.AtomicLong;
24 import java.util.concurrent.atomic.AtomicReference;
25 import org.opendaylight.controller.config.yang.config.toaster_provider.impl.ToasterProviderRuntimeMXBean;
26 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
27 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
28 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
29 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
30 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
31 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
32 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
33 import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
34 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
35 import org.opendaylight.controller.sal.binding.api.NotificationProviderService;
36 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.DisplayString;
37 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastInput;
38 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.RestockToasterInput;
39 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.Toaster;
40 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.Toaster.ToasterStatus;
41 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterBuilder;
42 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterOutOfBreadBuilder;
43 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestocked;
44 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestockedBuilder;
45 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterService;
46 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
47 import org.opendaylight.yangtools.yang.common.RpcError;
48 import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
49 import org.opendaylight.yangtools.yang.common.RpcResult;
50 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
54 public class OpendaylightToaster implements ToasterService, ToasterProviderRuntimeMXBean,
55 DataTreeChangeListener<Toaster>, AutoCloseable {
57 private static final Logger LOG = LoggerFactory.getLogger(OpendaylightToaster.class);
59 public static final InstanceIdentifier<Toaster> TOASTER_IID = InstanceIdentifier.builder(Toaster.class).build();
61 private static final DisplayString TOASTER_MANUFACTURER = new DisplayString("Opendaylight");
62 private static final DisplayString TOASTER_MODEL_NUMBER = new DisplayString("Model 1 - Binding Aware");
64 private NotificationProviderService notificationProvider;
65 private DataBroker dataProvider;
67 private final ExecutorService executor;
69 // The following holds the Future for the current make toast task.
70 // This is used to cancel the current toast.
71 private final AtomicReference<Future<?>> currentMakeToastTask = new AtomicReference<>();
73 private final AtomicLong amountOfBreadInStock = new AtomicLong( 100 );
75 private final AtomicLong toastsMade = new AtomicLong(0);
77 // Thread safe holder for our darkness multiplier.
78 private final AtomicLong darknessFactor = new AtomicLong( 1000 );
80 public OpendaylightToaster() {
81 executor = Executors.newFixedThreadPool(1);
84 public void setNotificationProvider(final NotificationProviderService salService) {
85 this.notificationProvider = salService;
88 public void setDataProvider(final DataBroker salDataProvider) {
89 this.dataProvider = salDataProvider;
90 setToasterStatusUp( null );
94 * Implemented from the AutoCloseable interface.
97 public void close() throws ExecutionException, InterruptedException {
98 // When we close this service we need to shutdown our executor!
101 if (dataProvider != null) {
102 WriteTransaction tx = dataProvider.newWriteOnlyTransaction();
103 tx.delete(LogicalDatastoreType.OPERATIONAL,TOASTER_IID);
104 Futures.addCallback( tx.submit(), new FutureCallback<Void>() {
106 public void onSuccess( final Void result ) {
107 LOG.debug( "Delete Toaster commit result: " + result );
111 public void onFailure( final Throwable t ) {
112 LOG.error( "Delete of Toaster failed", t );
118 private Toaster buildToaster( final ToasterStatus status ) {
120 // note - we are simulating a device whose manufacture and model are
121 // fixed (embedded) into the hardware.
122 // This is why the manufacture and model number are hardcoded.
123 return new ToasterBuilder().setToasterManufacturer( TOASTER_MANUFACTURER )
124 .setToasterModelNumber( TOASTER_MODEL_NUMBER )
125 .setToasterStatus( status )
130 * Implemented from the DataTreeChangeListener interface.
133 public void onDataTreeChanged(Collection<DataTreeModification<Toaster>> changes) {
134 for(DataTreeModification<Toaster> change: changes) {
135 DataObjectModification<Toaster> rootNode = change.getRootNode();
136 if(rootNode.getModificationType() == DataObjectModification.ModificationType.WRITE) {
137 Toaster oldToaster = rootNode.getDataBefore();
138 Toaster newToaster = rootNode.getDataAfter();
139 LOG.info("onDataTreeChanged - Toaster config with path {} was added or replaced: old Toaster: {}, new Toaster: {}",
140 change.getRootPath().getRootIdentifier(), oldToaster, newToaster);
142 Long darkness = newToaster.getDarknessFactor();
143 if(darkness != null) {
144 darknessFactor.set(darkness);
146 } else if(rootNode.getModificationType() == DataObjectModification.ModificationType.DELETE) {
147 LOG.info("onDataTreeChanged - Toaster config with path {} was deleted: old Toaster: {}",
148 change.getRootPath().getRootIdentifier(), rootNode.getDataBefore());
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( RpcResultBuilder.<Void> success().build() );
170 * RPC call implemented from the ToasterService interface that attempts to make toast.
173 public Future<RpcResult<Void>> makeToast(final MakeToastInput input) {
174 LOG.info("makeToast: " + input);
176 final SettableFuture<RpcResult<Void>> futureResult = SettableFuture.create();
178 checkStatusAndMakeToast( input, futureResult, 2 );
183 private RpcError makeToasterOutOfBreadError() {
184 return RpcResultBuilder.newError( ErrorType.APPLICATION, "resource-denied",
185 "Toaster is out of bread", "out-of-stock", null, null );
188 private RpcError makeToasterInUseError() {
189 return RpcResultBuilder.newWarning( ErrorType.APPLICATION, "in-use",
190 "Toaster is busy", null, null, null );
193 private void checkStatusAndMakeToast( final MakeToastInput input,
194 final SettableFuture<RpcResult<Void>> futureResult,
197 // Read the ToasterStatus and, if currently Up, try to write the status to Down.
198 // If that succeeds, then we essentially have an exclusive lock and can proceed
201 final ReadWriteTransaction tx = dataProvider.newReadWriteTransaction();
202 ListenableFuture<Optional<Toaster>> readFuture =
203 tx.read( LogicalDatastoreType.OPERATIONAL, TOASTER_IID );
205 final ListenableFuture<Void> commitFuture =
206 Futures.transform( readFuture, new AsyncFunction<Optional<Toaster>,Void>() {
209 public ListenableFuture<Void> apply(
210 final Optional<Toaster> toasterData ) throws Exception {
212 ToasterStatus toasterStatus = ToasterStatus.Up;
213 if( toasterData.isPresent() ) {
214 toasterStatus = toasterData.get().getToasterStatus();
217 LOG.debug( "Read toaster status: {}", toasterStatus );
219 if( toasterStatus == ToasterStatus.Up ) {
222 LOG.debug( "Toaster is out of bread" );
224 return Futures.immediateFailedCheckedFuture(
225 new TransactionCommitFailedException( "", makeToasterOutOfBreadError() ) );
228 LOG.debug( "Setting Toaster status to Down" );
230 // We're not currently making toast - try to update the status to Down
231 // to indicate we're going to make toast. This acts as a lock to prevent
232 // concurrent toasting.
233 tx.put( LogicalDatastoreType.OPERATIONAL, TOASTER_IID,
234 buildToaster( ToasterStatus.Down ) );
238 LOG.debug( "Oops - already making toast!" );
240 // Return an error since we are already making toast. This will get
241 // propagated to the commitFuture below which will interpret the null
242 // TransactionStatus in the RpcResult as an error condition.
243 return Futures.immediateFailedCheckedFuture(
244 new TransactionCommitFailedException( "", makeToasterInUseError() ) );
248 Futures.addCallback( commitFuture, new FutureCallback<Void>() {
250 public void onSuccess( final Void result ) {
252 currentMakeToastTask.set( executor.submit( new MakeToastTask( input, futureResult ) ) );
256 public void onFailure( final Throwable ex ) {
257 if( ex instanceof OptimisticLockFailedException ) {
259 // Another thread is likely trying to make toast simultaneously and updated the
260 // status before us. Try reading the status again - if another make toast is
261 // now in progress, we should get ToasterStatus.Down and fail.
263 if( ( tries - 1 ) > 0 ) {
264 LOG.debug( "Got OptimisticLockFailedException - trying again" );
266 checkStatusAndMakeToast( input, futureResult, tries - 1 );
269 futureResult.set( RpcResultBuilder.<Void> failed()
270 .withError( ErrorType.APPLICATION, ex.getMessage() ).build() );
275 LOG.debug( "Failed to commit Toaster status", ex );
277 // Probably already making toast.
278 futureResult.set( RpcResultBuilder.<Void> failed()
279 .withRpcErrors( ((TransactionCommitFailedException)ex).getErrorList() )
287 * RestConf RPC call implemented from the ToasterService interface.
288 * Restocks the bread for the toaster, resets the toastsMade counter to 0, and sends a
289 * ToasterRestocked notification.
292 public Future<RpcResult<java.lang.Void>> restockToaster(final RestockToasterInput input) {
293 LOG.info( "restockToaster: " + input );
295 amountOfBreadInStock.set( input.getAmountOfBreadToStock() );
297 if( amountOfBreadInStock.get() > 0 ) {
298 ToasterRestocked reStockedNotification = new ToasterRestockedBuilder()
299 .setAmountOfBread( input.getAmountOfBreadToStock() ).build();
300 notificationProvider.publish( reStockedNotification );
303 return Futures.immediateFuture( RpcResultBuilder.<Void> success().build() );
307 * JMX RPC call implemented from the ToasterProviderRuntimeMXBean interface.
310 public void clearToastsMade() {
311 LOG.info( "clearToastsMade" );
316 * Accesssor method implemented from the ToasterProviderRuntimeMXBean interface.
319 public Long getToastsMade() {
320 return toastsMade.get();
323 private void setToasterStatusUp( final Function<Boolean,Void> resultCallback ) {
325 WriteTransaction tx = dataProvider.newWriteOnlyTransaction();
326 tx.put( LogicalDatastoreType.OPERATIONAL,TOASTER_IID, buildToaster( ToasterStatus.Up ) );
328 Futures.addCallback( tx.submit(), new FutureCallback<Void>() {
330 public void onSuccess( final Void result ) {
331 notifyCallback( true );
335 public void onFailure( final Throwable t ) {
336 // We shouldn't get an OptimisticLockFailedException (or any ex) as no
337 // other component should be updating the operational state.
338 LOG.error( "Failed to update toaster status", t );
340 notifyCallback( false );
343 void notifyCallback( final boolean result ) {
344 if( resultCallback != null ) {
345 resultCallback.apply( result );
351 private boolean outOfBread()
353 return amountOfBreadInStock.get() == 0;
356 private class MakeToastTask implements Callable<Void> {
358 final MakeToastInput toastRequest;
359 final SettableFuture<RpcResult<Void>> futureResult;
361 public MakeToastTask( final MakeToastInput toastRequest,
362 final SettableFuture<RpcResult<Void>> futureResult ) {
363 this.toastRequest = toastRequest;
364 this.futureResult = futureResult;
371 // make toast just sleeps for n seconds per doneness level.
372 long darknessFactor = OpendaylightToaster.this.darknessFactor.get();
373 Thread.sleep(darknessFactor * toastRequest.getToasterDoneness());
376 catch( InterruptedException e ) {
377 LOG.info( "Interrupted while making the toast" );
380 toastsMade.incrementAndGet();
382 amountOfBreadInStock.getAndDecrement();
384 LOG.info( "Toaster is out of bread!" );
386 notificationProvider.publish( new ToasterOutOfBreadBuilder().build() );
389 // Set the Toaster status back to up - this essentially releases the toasting lock.
390 // We can't clear the current toast task nor set the Future result until the
391 // update has been committed so we pass a callback to be notified on completion.
393 setToasterStatusUp( new Function<Boolean,Void>() {
395 public Void apply( final Boolean result ) {
397 currentMakeToastTask.set( null );
399 LOG.debug("Toast done");
401 futureResult.set( RpcResultBuilder.<Void>success().build() );