/** * Copyright 2011, Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. **/ package net.floodlightcontroller.storage; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import net.floodlightcontroller.core.annotations.LogMessageCategory; import net.floodlightcontroller.core.annotations.LogMessageDoc; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.counter.ICounter; import net.floodlightcontroller.counter.CounterStore; import net.floodlightcontroller.counter.ICounterStoreService; import net.floodlightcontroller.counter.CounterValue.CounterType; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.storage.web.StorageWebRoutable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @LogMessageCategory("System Database") public abstract class AbstractStorageSource implements IStorageSourceService, IFloodlightModule { protected static Logger logger = LoggerFactory.getLogger(AbstractStorageSource.class); // Shared instance of the executor to use to execute the storage tasks. // We make this a single threaded executor, because if we used a thread pool // then storage operations could be executed out of order which would cause // problems in some cases (e.g. delete and update of a row getting reordered). // If we wanted to make this more multi-threaded we could have multiple // worker threads/executors with affinity of operations on a given table // to a single worker thread. But for now, we'll keep it simple and just have // a single thread for all operations. protected static ExecutorService defaultExecutorService = Executors.newSingleThreadExecutor(); protected final static String STORAGE_QUERY_COUNTER_NAME = "StorageQuery"; protected final static String STORAGE_UPDATE_COUNTER_NAME = "StorageUpdate"; protected final static String STORAGE_DELETE_COUNTER_NAME = "StorageDelete"; protected Set<String> allTableNames = new CopyOnWriteArraySet<String>(); protected ICounterStoreService counterStore; protected ExecutorService executorService = defaultExecutorService; protected IStorageExceptionHandler exceptionHandler; private Map<String, Set<IStorageSourceListener>> listeners = new ConcurrentHashMap<String, Set<IStorageSourceListener>>(); // Our dependencies protected IRestApiService restApi = null; protected static final String DB_ERROR_EXPLANATION = "An unknown error occurred while executing asynchronous " + "database operation"; @LogMessageDoc(level="ERROR", message="Failure in asynchronous call to executeQuery", explanation=DB_ERROR_EXPLANATION, recommendation=LogMessageDoc.GENERIC_ACTION) abstract class StorageCallable<V> implements Callable<V> { public V call() { try { return doStorageOperation(); } catch (StorageException e) { logger.error("Failure in asynchronous call to executeQuery", e); if (exceptionHandler != null) exceptionHandler.handleException(e); throw e; } } abstract protected V doStorageOperation(); } @LogMessageDoc(level="ERROR", message="Failure in asynchronous call to updateRows", explanation=DB_ERROR_EXPLANATION, recommendation=LogMessageDoc.GENERIC_ACTION) abstract class StorageRunnable implements Runnable { public void run() { try { doStorageOperation(); } catch (StorageException e) { logger.error("Failure in asynchronous call to updateRows", e); if (exceptionHandler != null) exceptionHandler.handleException(e); throw e; } } abstract void doStorageOperation(); } public AbstractStorageSource() { this.executorService = defaultExecutorService; } public void setExecutorService(ExecutorService executorService) { this.executorService = (executorService != null) ? executorService : defaultExecutorService; } @Override public void setExceptionHandler(IStorageExceptionHandler exceptionHandler) { this.exceptionHandler = exceptionHandler; } @Override public abstract void setTablePrimaryKeyName(String tableName, String primaryKeyName); @Override public void createTable(String tableName, Set<String> indexedColumns) { allTableNames.add(tableName); } @Override public Set<String> getAllTableNames() { return allTableNames; } public void setCounterStore(CounterStore counterStore) { this.counterStore = counterStore; } protected void updateCounters(String baseName, String tableName) { if (counterStore != null) { String counterName; if (tableName != null) { updateCounters(baseName, null); counterName = baseName + CounterStore.TitleDelimitor + tableName; } else { counterName = baseName; } ICounter counter = counterStore.getCounter(counterName); if (counter == null) { counter = counterStore.createCounter(counterName, CounterType.LONG); } counter.increment(); } } @Override public abstract IQuery createQuery(String tableName, String[] columnNames, IPredicate predicate, RowOrdering ordering); @Override public IResultSet executeQuery(IQuery query) { updateCounters(STORAGE_QUERY_COUNTER_NAME, query.getTableName()); return executeQueryImpl(query); } protected abstract IResultSet executeQueryImpl(IQuery query); @Override public IResultSet executeQuery(String tableName, String[] columnNames, IPredicate predicate, RowOrdering ordering) { IQuery query = createQuery(tableName, columnNames, predicate, ordering); IResultSet resultSet = executeQuery(query); return resultSet; } @Override public Object[] executeQuery(String tableName, String[] columnNames, IPredicate predicate, RowOrdering ordering, IRowMapper rowMapper) { List<Object> objectList = new ArrayList<Object>(); IResultSet resultSet = executeQuery(tableName, columnNames, predicate, ordering); while (resultSet.next()) { Object object = rowMapper.mapRow(resultSet); objectList.add(object); } return objectList.toArray(); } @Override public Future<IResultSet> executeQueryAsync(final IQuery query) { Future<IResultSet> future = executorService.submit( new StorageCallable<IResultSet>() { public IResultSet doStorageOperation() { return executeQuery(query); } }); return future; } @Override public Future<IResultSet> executeQueryAsync(final String tableName, final String[] columnNames, final IPredicate predicate, final RowOrdering ordering) { Future<IResultSet> future = executorService.submit( new StorageCallable<IResultSet>() { public IResultSet doStorageOperation() { return executeQuery(tableName, columnNames, predicate, ordering); } }); return future; } @Override public Future<Object[]> executeQueryAsync(final String tableName, final String[] columnNames, final IPredicate predicate, final RowOrdering ordering, final IRowMapper rowMapper) { Future<Object[]> future = executorService.submit( new StorageCallable<Object[]>() { public Object[] doStorageOperation() { return executeQuery(tableName, columnNames, predicate, ordering, rowMapper); } }); return future; } @Override public Future<?> insertRowAsync(final String tableName, final Map<String,Object> values) { Future<?> future = executorService.submit( new StorageRunnable() { public void doStorageOperation() { insertRow(tableName, values); } }, null); return future; } @Override public Future<?> updateRowsAsync(final String tableName, final List<Map<String,Object>> rows) { Future<?> future = executorService.submit( new StorageRunnable() { public void doStorageOperation() { updateRows(tableName, rows); } }, null); return future; } @Override public Future<?> updateMatchingRowsAsync(final String tableName, final IPredicate predicate, final Map<String,Object> values) { Future<?> future = executorService.submit( new StorageRunnable() { public void doStorageOperation() { updateMatchingRows(tableName, predicate, values); } }, null); return future; } @Override public Future<?> updateRowAsync(final String tableName, final Object rowKey, final Map<String,Object> values) { Future<?> future = executorService.submit( new StorageRunnable() { public void doStorageOperation() { updateRow(tableName, rowKey, values); } }, null); return future; } @Override public Future<?> updateRowAsync(final String tableName, final Map<String,Object> values) { Future<?> future = executorService.submit( new StorageRunnable() { public void doStorageOperation() { updateRow(tableName, values); } }, null); return future; } @Override public Future<?> deleteRowAsync(final String tableName, final Object rowKey) { Future<?> future = executorService.submit( new StorageRunnable() { public void doStorageOperation() { deleteRow(tableName, rowKey); } }, null); return future; } @Override public Future<?> deleteRowsAsync(final String tableName, final Set<Object> rowKeys) { Future<?> future = executorService.submit( new StorageRunnable() { public void doStorageOperation() { deleteRows(tableName, rowKeys); } }, null); return future; } @Override public Future<?> deleteMatchingRowsAsync(final String tableName, final IPredicate predicate) { Future<?> future = executorService.submit( new StorageRunnable() { public void doStorageOperation() { deleteMatchingRows(tableName, predicate); } }, null); return future; } @Override public Future<?> getRowAsync(final String tableName, final Object rowKey) { Future<?> future = executorService.submit( new StorageRunnable() { public void doStorageOperation() { getRow(tableName, rowKey); } }, null); return future; } @Override public Future<?> saveAsync(final IResultSet resultSet) { Future<?> future = executorService.submit( new StorageRunnable() { public void doStorageOperation() { resultSet.save(); } }, null); return future; } @Override public void insertRow(String tableName, Map<String, Object> values) { updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName); insertRowImpl(tableName, values); } protected abstract void insertRowImpl(String tableName, Map<String, Object> values); @Override public void updateRows(String tableName, List<Map<String,Object>> rows) { updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName); updateRowsImpl(tableName, rows); } protected abstract void updateRowsImpl(String tableName, List<Map<String,Object>> rows); @Override public void updateMatchingRows(String tableName, IPredicate predicate, Map<String, Object> values) { updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName); updateMatchingRowsImpl(tableName, predicate, values); } protected abstract void updateMatchingRowsImpl(String tableName, IPredicate predicate, Map<String, Object> values); @Override public void updateRow(String tableName, Object rowKey, Map<String, Object> values) { updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName); updateRowImpl(tableName, rowKey, values); } protected abstract void updateRowImpl(String tableName, Object rowKey, Map<String, Object> values); @Override public void updateRow(String tableName, Map<String, Object> values) { updateCounters(STORAGE_UPDATE_COUNTER_NAME, tableName); updateRowImpl(tableName, values); } protected abstract void updateRowImpl(String tableName, Map<String, Object> values); @Override public void deleteRow(String tableName, Object rowKey) { updateCounters(STORAGE_DELETE_COUNTER_NAME, tableName); deleteRowImpl(tableName, rowKey); } protected abstract void deleteRowImpl(String tableName, Object rowKey); @Override public void deleteRows(String tableName, Set<Object> rowKeys) { updateCounters(STORAGE_DELETE_COUNTER_NAME, tableName); deleteRowsImpl(tableName, rowKeys); } protected abstract void deleteRowsImpl(String tableName, Set<Object> rowKeys); @Override public void deleteMatchingRows(String tableName, IPredicate predicate) { IResultSet resultSet = null; try { resultSet = executeQuery(tableName, null, predicate, null); while (resultSet.next()) { resultSet.deleteRow(); } resultSet.save(); } finally { if (resultSet != null) resultSet.close(); } } @Override public IResultSet getRow(String tableName, Object rowKey) { updateCounters(STORAGE_QUERY_COUNTER_NAME, tableName); return getRowImpl(tableName, rowKey); } protected abstract IResultSet getRowImpl(String tableName, Object rowKey); @Override public synchronized void addListener(String tableName, IStorageSourceListener listener) { Set<IStorageSourceListener> tableListeners = listeners.get(tableName); if (tableListeners == null) { tableListeners = new CopyOnWriteArraySet<IStorageSourceListener>(); listeners.put(tableName, tableListeners); } tableListeners.add(listener); } @Override public synchronized void removeListener(String tableName, IStorageSourceListener listener) { Set<IStorageSourceListener> tableListeners = listeners.get(tableName); if (tableListeners != null) { tableListeners.remove(listener); } } @LogMessageDoc(level="ERROR", message="Exception caught handling storage notification", explanation="An unknown error occured while trying to notify" + " storage listeners", recommendation=LogMessageDoc.GENERIC_ACTION) protected synchronized void notifyListeners(StorageSourceNotification notification) { if (logger.isTraceEnabled()) { logger.trace("Notifying storage listeneres: {}", notification); } String tableName = notification.getTableName(); Set<Object> keys = notification.getKeys(); Set<IStorageSourceListener> tableListeners = listeners.get(tableName); if (tableListeners != null) { for (IStorageSourceListener listener : tableListeners) { try { switch (notification.getAction()) { case MODIFY: listener.rowsModified(tableName, keys); break; case DELETE: listener.rowsDeleted(tableName, keys); break; } } catch (Exception e) { logger.error("Exception caught handling storage notification", e); } } } } @Override public void notifyListeners(List<StorageSourceNotification> notifications) { for (StorageSourceNotification notification : notifications) notifyListeners(notification); } // IFloodlightModule @Override public Collection<Class<? extends IFloodlightService>> getModuleServices() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IStorageSourceService.class); return l; } @Override public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); m.put(IStorageSourceService.class, this); return m; } @Override public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IRestApiService.class); l.add(ICounterStoreService.class); return l; } @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { restApi = context.getServiceImpl(IRestApiService.class); counterStore = context.getServiceImpl(ICounterStoreService.class); } @Override public void startUp(FloodlightModuleContext context) { restApi.addRestletRoutable(new StorageWebRoutable()); } }