blob: 37d8a7fddcc57df451ac4e3502d9d98020b907ae [file] [log] [blame]
Thomas Vachuska96d55b12015-05-11 08:52:03 -07001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.incubator.store.config.impl;
17
18import com.fasterxml.jackson.databind.ObjectMapper;
Jonathan Hart111b42b2015-07-14 13:28:05 -070019import com.fasterxml.jackson.databind.node.ArrayNode;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070020import com.fasterxml.jackson.databind.node.BooleanNode;
21import com.fasterxml.jackson.databind.node.DoubleNode;
22import com.fasterxml.jackson.databind.node.JsonNodeFactory;
23import com.fasterxml.jackson.databind.node.LongNode;
24import com.fasterxml.jackson.databind.node.ObjectNode;
25import com.fasterxml.jackson.databind.node.ShortNode;
26import com.fasterxml.jackson.databind.node.TextNode;
27import com.google.common.collect.ImmutableSet;
28import com.google.common.collect.Maps;
29import org.apache.felix.scr.annotations.Activate;
30import org.apache.felix.scr.annotations.Component;
31import org.apache.felix.scr.annotations.Deactivate;
32import org.apache.felix.scr.annotations.Reference;
33import org.apache.felix.scr.annotations.ReferenceCardinality;
34import org.apache.felix.scr.annotations.Service;
35import org.onlab.util.KryoNamespace;
Thomas Vachuska5f2cbe62015-07-30 15:10:34 -070036import org.onlab.util.Tools;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070037import org.onosproject.incubator.net.config.Config;
38import org.onosproject.incubator.net.config.ConfigApplyDelegate;
39import org.onosproject.incubator.net.config.ConfigFactory;
40import org.onosproject.incubator.net.config.NetworkConfigEvent;
41import org.onosproject.incubator.net.config.NetworkConfigStore;
42import org.onosproject.incubator.net.config.NetworkConfigStoreDelegate;
43import org.onosproject.store.AbstractStore;
44import org.onosproject.store.serializers.KryoNamespaces;
45import org.onosproject.store.service.ConsistentMap;
Thomas Vachuska5f2cbe62015-07-30 15:10:34 -070046import org.onosproject.store.service.ConsistentMapException;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070047import org.onosproject.store.service.MapEvent;
48import org.onosproject.store.service.MapEventListener;
49import org.onosproject.store.service.Serializer;
50import org.onosproject.store.service.StorageService;
51import org.onosproject.store.service.Versioned;
52import org.slf4j.Logger;
53import org.slf4j.LoggerFactory;
54
55import java.util.LinkedHashMap;
56import java.util.Map;
57import java.util.Objects;
58import java.util.Set;
59
60import static org.onosproject.incubator.net.config.NetworkConfigEvent.Type.*;
61
62/**
63 * Implementation of a distributed network configuration store.
64 */
65@Component(immediate = true)
66@Service
67public class DistributedNetworkConfigStore
68 extends AbstractStore<NetworkConfigEvent, NetworkConfigStoreDelegate>
69 implements NetworkConfigStore {
70
Thomas Vachuska5f2cbe62015-07-30 15:10:34 -070071 private static final int MAX_BACKOFF = 10;
72
Thomas Vachuska96d55b12015-05-11 08:52:03 -070073 private final Logger log = LoggerFactory.getLogger(getClass());
74
75 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
76 protected StorageService storageService;
77
78 private ConsistentMap<ConfigKey, ObjectNode> configs;
79
80 private final Map<String, ConfigFactory> factoriesByConfig = Maps.newConcurrentMap();
81 private final ObjectMapper mapper = new ObjectMapper();
82 private final ConfigApplyDelegate applyDelegate = new InternalApplyDelegate();
83 private final MapEventListener<ConfigKey, ObjectNode> listener = new InternalMapListener();
84
85 @Activate
86 public void activate() {
87 KryoNamespace.Builder kryoBuilder = new KryoNamespace.Builder()
88 .register(KryoNamespaces.API)
Jonathan Hart111b42b2015-07-14 13:28:05 -070089 .register(ConfigKey.class, ObjectNode.class, ArrayNode.class,
Thomas Vachuska96d55b12015-05-11 08:52:03 -070090 JsonNodeFactory.class, LinkedHashMap.class,
91 TextNode.class, BooleanNode.class,
92 LongNode.class, DoubleNode.class, ShortNode.class);
93
94 configs = storageService.<ConfigKey, ObjectNode>consistentMapBuilder()
95 .withSerializer(Serializer.using(kryoBuilder.build()))
96 .withName("onos-network-configs")
Madan Jampani3d6a2f62015-08-12 07:19:07 -070097 .withRelaxedReadConsistency()
Thomas Vachuska96d55b12015-05-11 08:52:03 -070098 .build();
99 configs.addListener(listener);
100 log.info("Started");
101 }
102
103 @Deactivate
104 public void deactivate() {
105 configs.removeListener(listener);
106 log.info("Stopped");
107 }
108
109 @Override
110 public void addConfigFactory(ConfigFactory configFactory) {
111 factoriesByConfig.put(configFactory.configClass().getName(), configFactory);
Thomas Vachuskae6360222015-07-21 10:10:36 -0700112 notifyDelegate(new NetworkConfigEvent(CONFIG_REGISTERED, configFactory.configKey(),
113 configFactory.configClass()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700114 }
115
116 @Override
117 public void removeConfigFactory(ConfigFactory configFactory) {
118 factoriesByConfig.remove(configFactory.configClass().getName());
Thomas Vachuskae6360222015-07-21 10:10:36 -0700119 notifyDelegate(new NetworkConfigEvent(CONFIG_UNREGISTERED, configFactory.configKey(),
120 configFactory.configClass()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700121 }
122
123 @Override
124 @SuppressWarnings("unchecked")
125 public <S, C extends Config<S>> ConfigFactory<S, C> getConfigFactory(Class<C> configClass) {
126 return (ConfigFactory<S, C>) factoriesByConfig.get(configClass.getName());
127 }
128
129 @Override
130 @SuppressWarnings("unchecked")
131 public <S> Set<S> getSubjects(Class<S> subjectClass) {
132 ImmutableSet.Builder<S> builder = ImmutableSet.builder();
133 configs.keySet().forEach(k -> {
134 if (subjectClass.isInstance(k.subject)) {
135 builder.add((S) k.subject);
136 }
137 });
138 return builder.build();
139 }
140
141 @Override
142 @SuppressWarnings("unchecked")
143 public <S, C extends Config<S>> Set<S> getSubjects(Class<S> subjectClass, Class<C> configClass) {
144 ImmutableSet.Builder<S> builder = ImmutableSet.builder();
145 String cName = configClass.getName();
146 configs.keySet().forEach(k -> {
147 if (subjectClass.isInstance(k.subject) && cName.equals(k.configClass)) {
148 builder.add((S) k.subject);
149 }
150 });
151 return builder.build();
152 }
153
154 @Override
155 @SuppressWarnings("unchecked")
156 public <S> Set<Class<? extends Config<S>>> getConfigClasses(S subject) {
157 ImmutableSet.Builder<Class<? extends Config<S>>> builder = ImmutableSet.builder();
158 configs.keySet().forEach(k -> {
159 if (Objects.equals(subject, k.subject) && delegate != null) {
160 builder.add(factoriesByConfig.get(k.configClass).configClass());
161 }
162 });
163 return builder.build();
164 }
165
166 @Override
167 public <S, T extends Config<S>> T getConfig(S subject, Class<T> configClass) {
Thomas Vachuska6224a192015-07-30 16:11:40 -0700168 // FIXME: There has to be a better way to absorb the timeout exceptions!
Thomas Vachuska5f2cbe62015-07-30 15:10:34 -0700169 Versioned<ObjectNode> json = null;
170 try {
171 json = configs.get(key(subject, configClass));
Thomas Vachuska6224a192015-07-30 16:11:40 -0700172 } catch (ConsistentMapException e) {
Thomas Vachuska5f2cbe62015-07-30 15:10:34 -0700173 Tools.randomDelay(MAX_BACKOFF);
174 json = configs.get(key(subject, configClass));
175 }
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700176 return json != null ? createConfig(subject, configClass, json.value()) : null;
177 }
178
179
180 @Override
181 public <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass) {
182 Versioned<ObjectNode> json = configs.computeIfAbsent(key(subject, configClass),
183 k -> mapper.createObjectNode());
184 return createConfig(subject, configClass, json.value());
185 }
186
187 @Override
188 public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, ObjectNode json) {
189 return createConfig(subject, configClass,
190 configs.putAndGet(key(subject, configClass), json).value());
191 }
192
193 @Override
194 public <S, C extends Config<S>> void clearConfig(S subject, Class<C> configClass) {
195 configs.remove(key(subject, configClass));
196 }
197
198 /**
199 * Produces a config from the specified subject, config class and raw JSON.
200 *
201 * @param subject config subject
202 * @param configClass config class
203 * @param json raw JSON data
204 * @return config object or null of no factory found or if the specified
205 * JSON is null
206 */
207 @SuppressWarnings("unchecked")
208 private <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass,
209 ObjectNode json) {
210 if (json != null) {
211 ConfigFactory<S, C> factory = factoriesByConfig.get(configClass.getName());
212 if (factory != null) {
213 C config = factory.createConfig();
214 config.init(subject, factory.configKey(), json, mapper, applyDelegate);
215 return config;
216 }
217 }
218 return null;
219 }
220
221
222 // Auxiliary delegate to receive notifications about changes applied to
223 // the network configuration - by the apps.
224 private class InternalApplyDelegate implements ConfigApplyDelegate {
225 @Override
226 public void onApply(Config config) {
227 configs.put(key(config.subject(), config.getClass()), config.node());
228 }
229 }
230
231 // Produces a key for uniquely tracking a subject config.
232 private static ConfigKey key(Object subject, Class<?> configClass) {
233 return new ConfigKey(subject, configClass);
234 }
235
236 // Auxiliary key to track subject configurations.
237 private static final class ConfigKey {
238 final Object subject;
239 final String configClass;
240
241 private ConfigKey(Object subject, Class<?> configClass) {
242 this.subject = subject;
243 this.configClass = configClass.getName();
244 }
245
246 @Override
247 public int hashCode() {
248 return Objects.hash(subject, configClass);
249 }
250
251 @Override
252 public boolean equals(Object obj) {
253 if (this == obj) {
254 return true;
255 }
256 if (obj instanceof ConfigKey) {
257 final ConfigKey other = (ConfigKey) obj;
258 return Objects.equals(this.subject, other.subject)
259 && Objects.equals(this.configClass, other.configClass);
260 }
261 return false;
262 }
263 }
264
265 private class InternalMapListener implements MapEventListener<ConfigKey, ObjectNode> {
266 @Override
267 public void event(MapEvent<ConfigKey, ObjectNode> event) {
268 NetworkConfigEvent.Type type;
269 switch (event.type()) {
270 case INSERT:
271 type = CONFIG_ADDED;
272 break;
273 case UPDATE:
274 type = CONFIG_UPDATED;
275 break;
276 case REMOVE:
277 default:
278 type = CONFIG_REMOVED;
279 break;
280 }
281 ConfigFactory factory = factoriesByConfig.get(event.key().configClass);
282 if (factory != null) {
283 notifyDelegate(new NetworkConfigEvent(type, event.key().subject,
284 factory.configClass()));
285 }
286 }
287 }
288}