Merge "Bug 809: Enhancements to the toaster example"
[controller.git] / opendaylight / md-sal / samples / toaster-provider / src / main / java / org / opendaylight / controller / sample / toaster / provider / OpendaylightToaster.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.sample.toaster.provider;
9
10 import java.util.Arrays;
11 import java.util.Collections;
12 import java.util.concurrent.Callable;
13 import java.util.concurrent.ExecutionException;
14 import java.util.concurrent.ExecutorService;
15 import java.util.concurrent.Executors;
16 import java.util.concurrent.Future;
17 import java.util.concurrent.atomic.AtomicLong;
18
19 import org.opendaylight.controller.config.yang.config.toaster_provider.impl.ToasterProviderRuntimeMXBean;
20 import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent;
21 import org.opendaylight.controller.sal.binding.api.NotificationProviderService;
22 import org.opendaylight.controller.sal.binding.api.data.DataBrokerService;
23 import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction;
24 import org.opendaylight.controller.sal.common.util.RpcErrors;
25 import org.opendaylight.controller.sal.common.util.Rpcs;
26 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.DisplayString;
27 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastInput;
28 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.Toaster;
29 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.Toaster.ToasterStatus;
30 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.RestockToasterInput;
31 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterBuilder;
32 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterOutOfBreadBuilder;
33 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestocked;
34 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestockedBuilder;
35 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterService;
36 import org.opendaylight.controller.sal.binding.api.data.DataChangeListener;
37 import org.opendaylight.yangtools.yang.binding.DataObject;
38 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
39 import org.opendaylight.yangtools.yang.common.RpcError;
40 import org.opendaylight.yangtools.yang.common.RpcResult;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import com.google.common.util.concurrent.Futures;
45
46 public class OpendaylightToaster implements ToasterService, ToasterProviderRuntimeMXBean,
47                                             DataChangeListener, AutoCloseable {
48
49     private static final Logger LOG = LoggerFactory.getLogger(OpendaylightToaster.class);
50
51     public static final InstanceIdentifier<Toaster> TOASTER_IID = InstanceIdentifier.builder(Toaster.class).build();
52
53     private static final DisplayString TOASTER_MANUFACTURER = new DisplayString("Opendaylight");
54     private static final DisplayString TOASTER_MODEL_NUMBER = new DisplayString("Model 1 - Binding Aware");
55
56     private NotificationProviderService notificationProvider;
57     private DataBrokerService dataProvider;
58
59     private final ExecutorService executor;
60
61     // As you will see we are using multiple threads here. Therefore we need to be careful about concurrency.
62     // In this case we use the taskLock to provide synchronization for the current task.
63     private volatile Future<RpcResult<Void>> currentTask;
64     private final Object taskLock = new Object();
65
66     private final AtomicLong amountOfBreadInStock = new AtomicLong( 100 );
67
68     private final AtomicLong toastsMade = new AtomicLong(0);
69
70     // Thread safe holder for our darkness multiplier.
71     private final AtomicLong darknessFactor = new AtomicLong( 1000 );
72
73     public OpendaylightToaster() {
74         executor = Executors.newFixedThreadPool(1);
75     }
76
77     public void setNotificationProvider(NotificationProviderService salService) {
78         this.notificationProvider = salService;
79     }
80
81     public void setDataProvider(DataBrokerService salDataProvider) {
82         this.dataProvider = salDataProvider;
83         updateStatus();
84     }
85
86     /**
87      * Implemented from the AutoCloseable interface.
88      */
89     @Override
90     public void close() throws ExecutionException, InterruptedException {
91         // When we close this service we need to shutdown our executor!
92         executor.shutdown();
93
94         if (dataProvider != null) {
95             final DataModificationTransaction t = dataProvider.beginTransaction();
96             t.removeOperationalData(TOASTER_IID);
97             t.commit().get();
98         }
99     }
100
101     private Toaster buildToaster() {
102         // We don't need to synchronize on currentTask here b/c it's declared volatile and
103         // we're just doing a read.
104         boolean isUp = currentTask == null;
105
106         // note - we are simulating a device whose manufacture and model are
107         // fixed (embedded) into the hardware.
108         // This is why the manufacture and model number are hardcoded.
109         ToasterBuilder tb = new ToasterBuilder();
110         tb.setToasterManufacturer(TOASTER_MANUFACTURER).setToasterModelNumber(TOASTER_MODEL_NUMBER)
111                 .setToasterStatus(isUp ? ToasterStatus.Up : ToasterStatus.Down);
112         return tb.build();
113     }
114
115     /**
116      * Implemented from the DataChangeListener interface.
117      */
118     @Override
119     public void onDataChanged( DataChangeEvent<InstanceIdentifier<?>, DataObject> change ) {
120         DataObject dataObject = change.getUpdatedConfigurationData().get( TOASTER_IID );
121         if( dataObject instanceof Toaster )
122         {
123             Toaster toaster = (Toaster) dataObject;
124             Long darkness = toaster.getDarknessFactor();
125             if( darkness != null )
126             {
127                 darknessFactor.set( darkness );
128             }
129         }
130     }
131
132     /**
133      * RestConf RPC call implemented from the ToasterService interface.
134      */
135     @Override
136     public Future<RpcResult<Void>> cancelToast() {
137         synchronized (taskLock) {
138             if (currentTask != null) {
139                 currentTask.cancel(true);
140                 currentTask = null;
141             }
142         }
143         // Always return success from the cancel toast call.
144         return Futures.immediateFuture(Rpcs.<Void> getRpcResult(true, Collections.<RpcError> emptySet()));
145     }
146
147     /**
148      * RestConf RPC call implemented from the ToasterService interface.
149      */
150     @Override
151     public Future<RpcResult<Void>> makeToast(MakeToastInput input) {
152         LOG.info("makeToast: " + input);
153
154         synchronized (taskLock) {
155             if (currentTask != null) {
156                 // return an error since we are already toasting some toast.
157                 LOG.info( "Toaster is already making toast" );
158
159                 RpcResult<Void> result = Rpcs.<Void> getRpcResult(false, null, Arrays.asList(
160                         RpcErrors.getRpcError( null, null, null, null,
161                                                "Toaster is busy", null, null ) ) );
162                 return Futures.immediateFuture(result);
163             }
164             else if( outOfBread() ) {
165                 RpcResult<Void> result = Rpcs.<Void> getRpcResult(false, null, Arrays.asList(
166                         RpcErrors.getRpcError( null, null, null, null,
167                                                "Toaster is out of bread", null, null ) ) );
168                 return Futures.immediateFuture(result);
169             }
170             else {
171                 // Notice that we are moving the actual call to another thread,
172                 // allowing this thread to return immediately.
173                 // The MD-SAL design encourages asynchronus programming. If the
174                 // caller needs to block until the call is
175                 // complete then they can leverage the blocking methods on the
176                 // Future interface.
177                 currentTask = executor.submit(new MakeToastTask(input));
178             }
179         }
180
181         updateStatus();
182         return currentTask;
183     }
184
185     /**
186      * RestConf RPC call implemented from the ToasterService interface.
187      * Restocks the bread for the toaster, resets the toastsMade counter to 0, and sends a
188      * ToasterRestocked notification.
189      */
190     @Override
191     public Future<RpcResult<java.lang.Void>> restockToaster(RestockToasterInput input) {
192         LOG.info( "restockToaster: " + input );
193
194         synchronized( taskLock ) {
195             amountOfBreadInStock.set( input.getAmountOfBreadToStock() );
196
197             if( amountOfBreadInStock.get() > 0 ) {
198                 ToasterRestocked reStockedNotification =
199                     new ToasterRestockedBuilder().setAmountOfBread( input.getAmountOfBreadToStock() ).build();
200                 notificationProvider.publish( reStockedNotification );
201             }
202         }
203
204         return Futures.immediateFuture(Rpcs.<Void> getRpcResult(true, Collections.<RpcError> emptySet()));
205     }
206
207     /**
208      * JMX RPC call implemented from the ToasterProviderRuntimeMXBean interface.
209      */
210     @Override
211     public void clearToastsMade() {
212         LOG.info( "clearToastsMade" );
213         toastsMade.set( 0 );
214     }
215
216     /**
217      * Accesssor method implemented from the ToasterProviderRuntimeMXBean interface.
218      */
219     @Override
220     public Long getToastsMade() {
221         return toastsMade.get();
222     }
223
224     private void updateStatus() {
225         if (dataProvider != null) {
226             final DataModificationTransaction t = dataProvider.beginTransaction();
227             t.removeOperationalData(TOASTER_IID);
228             t.putOperationalData(TOASTER_IID, buildToaster());
229
230             try {
231                 t.commit().get();
232             } catch (InterruptedException | ExecutionException e) {
233                 LOG.warn("Failed to update toaster status, operational otherwise", e);
234             }
235         } else {
236             LOG.trace("No data provider configured, not updating status");
237         }
238     }
239
240     private boolean outOfBread()
241     {
242         return amountOfBreadInStock.get() == 0;
243     }
244
245     private class MakeToastTask implements Callable<RpcResult<Void>> {
246
247         final MakeToastInput toastRequest;
248
249         public MakeToastTask(MakeToastInput toast) {
250             toastRequest = toast;
251         }
252
253         @Override
254         public RpcResult<Void> call() {
255             try
256             {
257                 // make toast just sleeps for n secondn per doneness level.
258                 long darknessFactor = OpendaylightToaster.this.darknessFactor.get();
259                 Thread.sleep(darknessFactor * toastRequest.getToasterDoneness());
260
261             }
262             catch( InterruptedException e ) {
263                 LOG.info( "Interrupted while making the toast" );
264             }
265
266             toastsMade.incrementAndGet();
267
268             amountOfBreadInStock.getAndDecrement();
269             if( outOfBread() ) {
270                 LOG.info( "Toaster is out of bread!" );
271
272                 notificationProvider.publish( new ToasterOutOfBreadBuilder().build() );
273             }
274
275             synchronized (taskLock) {
276                 currentTask = null;
277             }
278
279             updateStatus();
280
281             LOG.debug("Toast done");
282
283             return Rpcs.<Void> getRpcResult(true, null, Collections.<RpcError> emptySet());
284         }
285     }
286 }