blob: f2bee40e2969ea7c5d2e8dd5fc59978f5507295a [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 */
Thomas Vachuska4998caa2015-08-26 13:28:38 -070016package org.onosproject.store.config.impl;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070017
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070018import com.fasterxml.jackson.databind.JsonNode;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070019import com.fasterxml.jackson.databind.ObjectMapper;
Jonathan Hart111b42b2015-07-14 13:28:05 -070020import com.fasterxml.jackson.databind.node.ArrayNode;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070021import com.fasterxml.jackson.databind.node.BooleanNode;
22import com.fasterxml.jackson.databind.node.DoubleNode;
Ayaka Koshibe1a002512015-09-03 13:09:23 -070023import com.fasterxml.jackson.databind.node.IntNode;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070024import com.fasterxml.jackson.databind.node.JsonNodeFactory;
25import com.fasterxml.jackson.databind.node.LongNode;
HIGUCHI Yutad9fe3a32015-11-24 18:52:25 -080026import com.fasterxml.jackson.databind.node.NullNode;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070027import com.fasterxml.jackson.databind.node.ObjectNode;
28import com.fasterxml.jackson.databind.node.ShortNode;
29import com.fasterxml.jackson.databind.node.TextNode;
30import com.google.common.collect.ImmutableSet;
31import com.google.common.collect.Maps;
Thomas Vachuska6f350ed2016-01-08 09:53:03 -080032import com.google.common.collect.Sets;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070033import org.apache.felix.scr.annotations.Activate;
34import org.apache.felix.scr.annotations.Component;
35import org.apache.felix.scr.annotations.Deactivate;
36import org.apache.felix.scr.annotations.Reference;
37import org.apache.felix.scr.annotations.ReferenceCardinality;
38import org.apache.felix.scr.annotations.Service;
39import org.onlab.util.KryoNamespace;
Thomas Vachuska5f2cbe62015-07-30 15:10:34 -070040import org.onlab.util.Tools;
Ray Milkeya4122362015-08-18 15:19:08 -070041import org.onosproject.net.config.Config;
42import org.onosproject.net.config.ConfigApplyDelegate;
43import org.onosproject.net.config.ConfigFactory;
44import org.onosproject.net.config.NetworkConfigEvent;
45import org.onosproject.net.config.NetworkConfigStore;
46import org.onosproject.net.config.NetworkConfigStoreDelegate;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070047import org.onosproject.store.AbstractStore;
48import org.onosproject.store.serializers.KryoNamespaces;
49import org.onosproject.store.service.ConsistentMap;
Thomas Vachuska5f2cbe62015-07-30 15:10:34 -070050import org.onosproject.store.service.ConsistentMapException;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070051import org.onosproject.store.service.MapEvent;
52import org.onosproject.store.service.MapEventListener;
53import org.onosproject.store.service.Serializer;
54import org.onosproject.store.service.StorageService;
55import org.onosproject.store.service.Versioned;
56import org.slf4j.Logger;
57import org.slf4j.LoggerFactory;
58
59import java.util.LinkedHashMap;
60import java.util.Map;
61import java.util.Objects;
62import java.util.Set;
63
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080064import static com.google.common.base.Preconditions.checkArgument;
Ray Milkeya4122362015-08-18 15:19:08 -070065import static org.onosproject.net.config.NetworkConfigEvent.Type.*;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070066
67/**
68 * Implementation of a distributed network configuration store.
69 */
70@Component(immediate = true)
71@Service
72public class DistributedNetworkConfigStore
73 extends AbstractStore<NetworkConfigEvent, NetworkConfigStoreDelegate>
74 implements NetworkConfigStore {
75
76 private final Logger log = LoggerFactory.getLogger(getClass());
77
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080078 private static final int MAX_BACKOFF = 10;
79 private static final String INVALID_CONFIG_JSON =
80 "JSON node does not contain valid configuration";
81
Thomas Vachuska96d55b12015-05-11 08:52:03 -070082 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
83 protected StorageService storageService;
84
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070085 private ConsistentMap<ConfigKey, JsonNode> configs;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070086
87 private final Map<String, ConfigFactory> factoriesByConfig = Maps.newConcurrentMap();
88 private final ObjectMapper mapper = new ObjectMapper();
89 private final ConfigApplyDelegate applyDelegate = new InternalApplyDelegate();
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070090 private final MapEventListener<ConfigKey, JsonNode> listener = new InternalMapListener();
Thomas Vachuska96d55b12015-05-11 08:52:03 -070091
92 @Activate
93 public void activate() {
94 KryoNamespace.Builder kryoBuilder = new KryoNamespace.Builder()
95 .register(KryoNamespaces.API)
Jonathan Hart111b42b2015-07-14 13:28:05 -070096 .register(ConfigKey.class, ObjectNode.class, ArrayNode.class,
Thomas Vachuska96d55b12015-05-11 08:52:03 -070097 JsonNodeFactory.class, LinkedHashMap.class,
98 TextNode.class, BooleanNode.class,
HIGUCHI Yutad9fe3a32015-11-24 18:52:25 -080099 LongNode.class, DoubleNode.class, ShortNode.class, IntNode.class,
100 NullNode.class);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700101
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700102 configs = storageService.<ConfigKey, JsonNode>consistentMapBuilder()
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700103 .withSerializer(Serializer.using(kryoBuilder.build()))
104 .withName("onos-network-configs")
Madan Jampani3d6a2f62015-08-12 07:19:07 -0700105 .withRelaxedReadConsistency()
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700106 .build();
107 configs.addListener(listener);
108 log.info("Started");
109 }
110
111 @Deactivate
112 public void deactivate() {
113 configs.removeListener(listener);
114 log.info("Stopped");
115 }
116
117 @Override
118 public void addConfigFactory(ConfigFactory configFactory) {
119 factoriesByConfig.put(configFactory.configClass().getName(), configFactory);
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800120 processPendingConfigs(configFactory);
Thomas Vachuskae6360222015-07-21 10:10:36 -0700121 notifyDelegate(new NetworkConfigEvent(CONFIG_REGISTERED, configFactory.configKey(),
122 configFactory.configClass()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700123 }
124
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800125 // Sweep through any pending configurations, validate them and then prune them.
126 private void processPendingConfigs(ConfigFactory configFactory) {
127 Set<ConfigKey> toBePruned = Sets.newHashSet();
128 configs.keySet().forEach(k -> {
129 if (Objects.equals(k.configKey, configFactory.configKey())) {
130 validateConfig(k, configFactory, configs.get(k).value());
131 toBePruned.add(k); // Prune whether valid or not
132 }
133 });
134 toBePruned.forEach(configs::remove);
135 }
136
137 @SuppressWarnings("unchecked")
138 private void validateConfig(ConfigKey key, ConfigFactory configFactory, JsonNode json) {
HIGUCHI Yutaca2208d2016-02-18 15:03:08 -0800139 Object subject;
140 if (key.subject instanceof String) {
141 subject = configFactory.subjectFactory().createSubject((String) key.subject);
142 } else {
143 subject = key.subject;
144 }
145 Config config = createConfig(subject, configFactory.configClass(), json);
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800146 try {
147 checkArgument(config.isValid(), INVALID_CONFIG_JSON);
HIGUCHI Yutaca2208d2016-02-18 15:03:08 -0800148 configs.putAndGet(key(subject, configFactory.configClass()), json);
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800149 } catch (Exception e) {
150 log.warn("Failed to validate pending {} configuration for {}: {}",
HIGUCHI Yutaca2208d2016-02-18 15:03:08 -0800151 key.configKey, key.subject, json);
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800152 }
153 }
154
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700155 @Override
156 public void removeConfigFactory(ConfigFactory configFactory) {
157 factoriesByConfig.remove(configFactory.configClass().getName());
Thomas Vachuskae6360222015-07-21 10:10:36 -0700158 notifyDelegate(new NetworkConfigEvent(CONFIG_UNREGISTERED, configFactory.configKey(),
159 configFactory.configClass()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700160 }
161
162 @Override
163 @SuppressWarnings("unchecked")
164 public <S, C extends Config<S>> ConfigFactory<S, C> getConfigFactory(Class<C> configClass) {
HIGUCHI Yutaca2208d2016-02-18 15:03:08 -0800165 return factoriesByConfig.get(configClass.getName());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700166 }
167
168 @Override
169 @SuppressWarnings("unchecked")
170 public <S> Set<S> getSubjects(Class<S> subjectClass) {
171 ImmutableSet.Builder<S> builder = ImmutableSet.builder();
172 configs.keySet().forEach(k -> {
173 if (subjectClass.isInstance(k.subject)) {
174 builder.add((S) k.subject);
175 }
176 });
177 return builder.build();
178 }
179
180 @Override
181 @SuppressWarnings("unchecked")
182 public <S, C extends Config<S>> Set<S> getSubjects(Class<S> subjectClass, Class<C> configClass) {
183 ImmutableSet.Builder<S> builder = ImmutableSet.builder();
184 String cName = configClass.getName();
185 configs.keySet().forEach(k -> {
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800186 if (subjectClass.isInstance(k.subject) && Objects.equals(cName, k.configClass)) {
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700187 builder.add((S) k.subject);
188 }
189 });
190 return builder.build();
191 }
192
193 @Override
194 @SuppressWarnings("unchecked")
195 public <S> Set<Class<? extends Config<S>>> getConfigClasses(S subject) {
196 ImmutableSet.Builder<Class<? extends Config<S>>> builder = ImmutableSet.builder();
197 configs.keySet().forEach(k -> {
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800198 if (Objects.equals(subject, k.subject) && k.configClass != null && delegate != null) {
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700199 builder.add(factoriesByConfig.get(k.configClass).configClass());
200 }
201 });
202 return builder.build();
203 }
204
205 @Override
206 public <S, T extends Config<S>> T getConfig(S subject, Class<T> configClass) {
Madan Jampania29c6772015-08-17 13:17:07 -0700207 // TODO: need to identify and address the root cause for timeouts.
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700208 Versioned<JsonNode> json = Tools.retryable(configs::get, ConsistentMapException.class, 1, MAX_BACKOFF)
209 .apply(key(subject, configClass));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700210 return json != null ? createConfig(subject, configClass, json.value()) : null;
211 }
212
213
214 @Override
215 public <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700216 ConfigFactory<S, C> factory = getConfigFactory(configClass);
217 Versioned<JsonNode> json = configs.computeIfAbsent(key(subject, configClass),
218 k -> factory.isList() ?
219 mapper.createArrayNode() :
220 mapper.createObjectNode());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700221 return createConfig(subject, configClass, json.value());
222 }
223
224 @Override
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700225 public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, JsonNode json) {
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800226 // Create the configuration and validate it.
227 C config = createConfig(subject, configClass, json);
228 checkArgument(config.isValid(), INVALID_CONFIG_JSON);
229
230 // Insert the validated configuration and get it back.
231 Versioned<JsonNode> versioned = configs.putAndGet(key(subject, configClass), json);
232
233 // Re-create the config if for some reason what we attempted to put
234 // was supplanted by someone else already.
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800235 return versioned.value() == json ? config : createConfig(subject, configClass, versioned.value());
236 }
237
238 @Override
239 public <S> void queueConfig(S subject, String configKey, JsonNode json) {
240 configs.put(key(subject, configKey), json);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700241 }
242
243 @Override
244 public <S, C extends Config<S>> void clearConfig(S subject, Class<C> configClass) {
245 configs.remove(key(subject, configClass));
246 }
247
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800248 @Override
249 public <S> void clearQueuedConfig(S subject, String configKey) {
250 configs.remove(key(subject, configKey));
251 }
252
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700253 /**
254 * Produces a config from the specified subject, config class and raw JSON.
255 *
256 * @param subject config subject
257 * @param configClass config class
258 * @param json raw JSON data
259 * @return config object or null of no factory found or if the specified
260 * JSON is null
261 */
262 @SuppressWarnings("unchecked")
263 private <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass,
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700264 JsonNode json) {
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700265 if (json != null) {
266 ConfigFactory<S, C> factory = factoriesByConfig.get(configClass.getName());
267 if (factory != null) {
268 C config = factory.createConfig();
269 config.init(subject, factory.configKey(), json, mapper, applyDelegate);
270 return config;
271 }
272 }
273 return null;
274 }
275
Charles Chan023a8982016-02-04 11:00:41 -0800276 /**
277 * Produces a detached config from the specified subject, config class and
278 * raw JSON.
279 *
280 * A detached config can no longer be applied. This should be used only for
281 * passing the config object in the NetworkConfigEvent.
282 *
283 * @param subject config subject
284 * @param configClass config class
285 * @param json raw JSON data
286 * @return config object or null of no factory found or if the specified
287 * JSON is null
288 */
289 @SuppressWarnings("unchecked")
290 private Config createDetachedConfig(Object subject,
291 Class configClass, JsonNode json) {
292 if (json != null) {
293 ConfigFactory factory = factoriesByConfig.get(configClass.getName());
294 if (factory != null) {
295 Config config = factory.createConfig();
296 config.init(subject, factory.configKey(), json, mapper, null);
297 return config;
298 }
299 }
300 return null;
301 }
302
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700303
304 // Auxiliary delegate to receive notifications about changes applied to
305 // the network configuration - by the apps.
306 private class InternalApplyDelegate implements ConfigApplyDelegate {
307 @Override
308 public void onApply(Config config) {
309 configs.put(key(config.subject(), config.getClass()), config.node());
310 }
311 }
312
313 // Produces a key for uniquely tracking a subject config.
314 private static ConfigKey key(Object subject, Class<?> configClass) {
315 return new ConfigKey(subject, configClass);
316 }
317
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800318 // Produces a key for uniquely tracking a subject config.
319 private static ConfigKey key(Object subject, String configKey) {
320 return new ConfigKey(subject, configKey);
321 }
322
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700323 // Auxiliary key to track subject configurations.
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800324 // Keys with non-null configKey are pending configurations.
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700325 private static final class ConfigKey {
326 final Object subject;
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800327 final String configKey;
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700328 final String configClass;
329
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800330 // Create a key for pending configuration class
331 private ConfigKey(Object subject, String configKey) {
332 this.subject = subject;
333 this.configKey = configKey;
334 this.configClass = null;
335 }
336
337 // Create a key for registered class configuration
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700338 private ConfigKey(Object subject, Class<?> configClass) {
339 this.subject = subject;
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800340 this.configKey = null;
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700341 this.configClass = configClass.getName();
342 }
343
344 @Override
345 public int hashCode() {
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800346 return Objects.hash(subject, configKey, configClass);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700347 }
348
349 @Override
350 public boolean equals(Object obj) {
351 if (this == obj) {
352 return true;
353 }
354 if (obj instanceof ConfigKey) {
355 final ConfigKey other = (ConfigKey) obj;
356 return Objects.equals(this.subject, other.subject)
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800357 && Objects.equals(this.configKey, other.configKey)
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700358 && Objects.equals(this.configClass, other.configClass);
359 }
360 return false;
361 }
362 }
363
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700364 private class InternalMapListener implements MapEventListener<ConfigKey, JsonNode> {
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700365 @Override
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700366 public void event(MapEvent<ConfigKey, JsonNode> event) {
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800367 // Do not delegate pending configs.
368 if (event.key().configClass == null) {
369 return;
370 }
371
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700372 ConfigFactory factory = factoriesByConfig.get(event.key().configClass);
373 if (factory != null) {
Charles Chan023a8982016-02-04 11:00:41 -0800374 Object subject = event.key().subject;
375 Class configClass = factory.configClass();
376 Versioned<JsonNode> newValue = event.newValue();
377 Versioned<JsonNode> oldValue = event.oldValue();
378
379 Config config = (newValue != null) ?
380 createDetachedConfig(subject, configClass, newValue.value()) :
381 null;
382 Config prevConfig = (oldValue != null) ?
383 createDetachedConfig(subject, configClass, oldValue.value()) :
384 null;
385
386 NetworkConfigEvent.Type type;
387 switch (event.type()) {
388 case INSERT:
389 type = CONFIG_ADDED;
390 break;
391 case UPDATE:
392 type = CONFIG_UPDATED;
393 break;
394 case REMOVE:
395 default:
396 type = CONFIG_REMOVED;
397 break;
398 }
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700399 notifyDelegate(new NetworkConfigEvent(type, event.key().subject,
Charles Chan023a8982016-02-04 11:00:41 -0800400 config, prevConfig, factory.configClass()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700401 }
402 }
403 }
404}