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