blob: 5397f546659c89a8a8263384b293826c507a9cc5 [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;
32import org.apache.felix.scr.annotations.Activate;
33import org.apache.felix.scr.annotations.Component;
34import org.apache.felix.scr.annotations.Deactivate;
35import org.apache.felix.scr.annotations.Reference;
36import org.apache.felix.scr.annotations.ReferenceCardinality;
37import org.apache.felix.scr.annotations.Service;
38import org.onlab.util.KryoNamespace;
Thomas Vachuska5f2cbe62015-07-30 15:10:34 -070039import org.onlab.util.Tools;
Ray Milkeya4122362015-08-18 15:19:08 -070040import org.onosproject.net.config.Config;
41import org.onosproject.net.config.ConfigApplyDelegate;
42import org.onosproject.net.config.ConfigFactory;
43import org.onosproject.net.config.NetworkConfigEvent;
44import org.onosproject.net.config.NetworkConfigStore;
45import org.onosproject.net.config.NetworkConfigStoreDelegate;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070046import org.onosproject.store.AbstractStore;
47import org.onosproject.store.serializers.KryoNamespaces;
48import org.onosproject.store.service.ConsistentMap;
Thomas Vachuska5f2cbe62015-07-30 15:10:34 -070049import org.onosproject.store.service.ConsistentMapException;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070050import org.onosproject.store.service.MapEvent;
51import org.onosproject.store.service.MapEventListener;
52import org.onosproject.store.service.Serializer;
53import org.onosproject.store.service.StorageService;
54import org.onosproject.store.service.Versioned;
55import org.slf4j.Logger;
56import org.slf4j.LoggerFactory;
57
58import java.util.LinkedHashMap;
59import java.util.Map;
60import java.util.Objects;
61import java.util.Set;
62
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080063import static com.google.common.base.Preconditions.checkArgument;
Ray Milkeya4122362015-08-18 15:19:08 -070064import static org.onosproject.net.config.NetworkConfigEvent.Type.*;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070065
66/**
67 * Implementation of a distributed network configuration store.
68 */
69@Component(immediate = true)
70@Service
71public class DistributedNetworkConfigStore
72 extends AbstractStore<NetworkConfigEvent, NetworkConfigStoreDelegate>
73 implements NetworkConfigStore {
74
75 private final Logger log = LoggerFactory.getLogger(getClass());
76
Thomas Vachuskace0bbb32015-11-18 16:56:10 -080077 private static final int MAX_BACKOFF = 10;
78 private static final String INVALID_CONFIG_JSON =
79 "JSON node does not contain valid configuration";
80
Thomas Vachuska96d55b12015-05-11 08:52:03 -070081 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
82 protected StorageService storageService;
83
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070084 private ConsistentMap<ConfigKey, JsonNode> configs;
Thomas Vachuska96d55b12015-05-11 08:52:03 -070085
86 private final Map<String, ConfigFactory> factoriesByConfig = Maps.newConcurrentMap();
87 private final ObjectMapper mapper = new ObjectMapper();
88 private final ConfigApplyDelegate applyDelegate = new InternalApplyDelegate();
Thomas Vachuska0a400ea2015-09-04 11:25:03 -070089 private final MapEventListener<ConfigKey, JsonNode> listener = new InternalMapListener();
Thomas Vachuska96d55b12015-05-11 08:52:03 -070090
91 @Activate
92 public void activate() {
93 KryoNamespace.Builder kryoBuilder = new KryoNamespace.Builder()
94 .register(KryoNamespaces.API)
Jonathan Hart111b42b2015-07-14 13:28:05 -070095 .register(ConfigKey.class, ObjectNode.class, ArrayNode.class,
Thomas Vachuska96d55b12015-05-11 08:52:03 -070096 JsonNodeFactory.class, LinkedHashMap.class,
97 TextNode.class, BooleanNode.class,
HIGUCHI Yutad9fe3a32015-11-24 18:52:25 -080098 LongNode.class, DoubleNode.class, ShortNode.class, IntNode.class,
99 NullNode.class);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700100
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700101 configs = storageService.<ConfigKey, JsonNode>consistentMapBuilder()
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700102 .withSerializer(Serializer.using(kryoBuilder.build()))
103 .withName("onos-network-configs")
Madan Jampani3d6a2f62015-08-12 07:19:07 -0700104 .withRelaxedReadConsistency()
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700105 .build();
106 configs.addListener(listener);
107 log.info("Started");
108 }
109
110 @Deactivate
111 public void deactivate() {
112 configs.removeListener(listener);
113 log.info("Stopped");
114 }
115
116 @Override
117 public void addConfigFactory(ConfigFactory configFactory) {
118 factoriesByConfig.put(configFactory.configClass().getName(), configFactory);
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800119 processPendingConfigs(configFactory);
Thomas Vachuskae6360222015-07-21 10:10:36 -0700120 notifyDelegate(new NetworkConfigEvent(CONFIG_REGISTERED, configFactory.configKey(),
121 configFactory.configClass()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700122 }
123
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800124 // Sweep through any pending configurations, validate them and then prune them.
125 private void processPendingConfigs(ConfigFactory configFactory) {
Thomas Vachuska096bcc82016-03-07 21:30:29 -0800126 ImmutableSet.copyOf(configs.keySet()).forEach(k -> {
127 if (Objects.equals(k.configKey, configFactory.configKey()) &&
128 isAssignableFrom(configFactory, k)) {
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800129 validateConfig(k, configFactory, configs.get(k).value());
Thomas Vachuska096bcc82016-03-07 21:30:29 -0800130 configs.remove(k); // Prune whether valid or not
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800131 }
132 });
Thomas Vachuska096bcc82016-03-07 21:30:29 -0800133 }
134
135 @SuppressWarnings("unchecked")
136 private boolean isAssignableFrom(ConfigFactory configFactory, ConfigKey k) {
137 return configFactory.subjectFactory().subjectClass().isAssignableFrom(k.subject.getClass());
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800138 }
139
140 @SuppressWarnings("unchecked")
141 private void validateConfig(ConfigKey key, ConfigFactory configFactory, JsonNode json) {
HIGUCHI Yutaca2208d2016-02-18 15:03:08 -0800142 Object subject;
143 if (key.subject instanceof String) {
144 subject = configFactory.subjectFactory().createSubject((String) key.subject);
145 } else {
146 subject = key.subject;
147 }
148 Config config = createConfig(subject, configFactory.configClass(), json);
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800149 try {
150 checkArgument(config.isValid(), INVALID_CONFIG_JSON);
HIGUCHI Yutaca2208d2016-02-18 15:03:08 -0800151 configs.putAndGet(key(subject, configFactory.configClass()), json);
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800152 } catch (Exception e) {
153 log.warn("Failed to validate pending {} configuration for {}: {}",
HIGUCHI Yutaca2208d2016-02-18 15:03:08 -0800154 key.configKey, key.subject, json);
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800155 }
156 }
157
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700158 @Override
159 public void removeConfigFactory(ConfigFactory configFactory) {
160 factoriesByConfig.remove(configFactory.configClass().getName());
Thomas Vachuskae6360222015-07-21 10:10:36 -0700161 notifyDelegate(new NetworkConfigEvent(CONFIG_UNREGISTERED, configFactory.configKey(),
162 configFactory.configClass()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700163 }
164
165 @Override
166 @SuppressWarnings("unchecked")
167 public <S, C extends Config<S>> ConfigFactory<S, C> getConfigFactory(Class<C> configClass) {
HIGUCHI Yutaca2208d2016-02-18 15:03:08 -0800168 return factoriesByConfig.get(configClass.getName());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700169 }
170
171 @Override
172 @SuppressWarnings("unchecked")
173 public <S> Set<S> getSubjects(Class<S> subjectClass) {
174 ImmutableSet.Builder<S> builder = ImmutableSet.builder();
175 configs.keySet().forEach(k -> {
176 if (subjectClass.isInstance(k.subject)) {
177 builder.add((S) k.subject);
178 }
179 });
180 return builder.build();
181 }
182
183 @Override
184 @SuppressWarnings("unchecked")
185 public <S, C extends Config<S>> Set<S> getSubjects(Class<S> subjectClass, Class<C> configClass) {
186 ImmutableSet.Builder<S> builder = ImmutableSet.builder();
187 String cName = configClass.getName();
188 configs.keySet().forEach(k -> {
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800189 if (subjectClass.isInstance(k.subject) && Objects.equals(cName, k.configClass)) {
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700190 builder.add((S) k.subject);
191 }
192 });
193 return builder.build();
194 }
195
196 @Override
197 @SuppressWarnings("unchecked")
198 public <S> Set<Class<? extends Config<S>>> getConfigClasses(S subject) {
199 ImmutableSet.Builder<Class<? extends Config<S>>> builder = ImmutableSet.builder();
200 configs.keySet().forEach(k -> {
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800201 if (Objects.equals(subject, k.subject) && k.configClass != null && delegate != null) {
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700202 builder.add(factoriesByConfig.get(k.configClass).configClass());
203 }
204 });
205 return builder.build();
206 }
207
208 @Override
209 public <S, T extends Config<S>> T getConfig(S subject, Class<T> configClass) {
Madan Jampania29c6772015-08-17 13:17:07 -0700210 // TODO: need to identify and address the root cause for timeouts.
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700211 Versioned<JsonNode> json = Tools.retryable(configs::get, ConsistentMapException.class, 1, MAX_BACKOFF)
212 .apply(key(subject, configClass));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700213 return json != null ? createConfig(subject, configClass, json.value()) : null;
214 }
215
216
217 @Override
218 public <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass) {
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700219 ConfigFactory<S, C> factory = getConfigFactory(configClass);
220 Versioned<JsonNode> json = configs.computeIfAbsent(key(subject, configClass),
221 k -> factory.isList() ?
222 mapper.createArrayNode() :
223 mapper.createObjectNode());
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700224 return createConfig(subject, configClass, json.value());
225 }
226
227 @Override
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700228 public <S, C extends Config<S>> C applyConfig(S subject, Class<C> configClass, JsonNode json) {
Thomas Vachuskace0bbb32015-11-18 16:56:10 -0800229 // Create the configuration and validate it.
230 C config = createConfig(subject, configClass, json);
231 checkArgument(config.isValid(), INVALID_CONFIG_JSON);
232
233 // Insert the validated configuration and get it back.
234 Versioned<JsonNode> versioned = configs.putAndGet(key(subject, configClass), json);
235
236 // Re-create the config if for some reason what we attempted to put
237 // was supplanted by someone else already.
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800238 return versioned.value() == json ? config : createConfig(subject, configClass, versioned.value());
239 }
240
241 @Override
242 public <S> void queueConfig(S subject, String configKey, JsonNode json) {
243 configs.put(key(subject, configKey), json);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700244 }
245
246 @Override
247 public <S, C extends Config<S>> void clearConfig(S subject, Class<C> configClass) {
248 configs.remove(key(subject, configClass));
249 }
250
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800251 @Override
252 public <S> void clearQueuedConfig(S subject, String configKey) {
253 configs.remove(key(subject, configKey));
254 }
255
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700256 /**
257 * Produces a config from the specified subject, config class and raw JSON.
258 *
259 * @param subject config subject
260 * @param configClass config class
261 * @param json raw JSON data
262 * @return config object or null of no factory found or if the specified
263 * JSON is null
264 */
265 @SuppressWarnings("unchecked")
266 private <S, C extends Config<S>> C createConfig(S subject, Class<C> configClass,
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700267 JsonNode json) {
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700268 if (json != null) {
269 ConfigFactory<S, C> factory = factoriesByConfig.get(configClass.getName());
270 if (factory != null) {
271 C config = factory.createConfig();
272 config.init(subject, factory.configKey(), json, mapper, applyDelegate);
273 return config;
274 }
275 }
276 return null;
277 }
278
Charles Chan023a8982016-02-04 11:00:41 -0800279 /**
280 * Produces a detached config from the specified subject, config class and
281 * raw JSON.
282 *
283 * A detached config can no longer be applied. This should be used only for
284 * passing the config object in the NetworkConfigEvent.
285 *
286 * @param subject config subject
287 * @param configClass config class
288 * @param json raw JSON data
289 * @return config object or null of no factory found or if the specified
290 * JSON is null
291 */
292 @SuppressWarnings("unchecked")
293 private Config createDetachedConfig(Object subject,
294 Class configClass, JsonNode json) {
295 if (json != null) {
296 ConfigFactory factory = factoriesByConfig.get(configClass.getName());
297 if (factory != null) {
298 Config config = factory.createConfig();
299 config.init(subject, factory.configKey(), json, mapper, null);
300 return config;
301 }
302 }
303 return null;
304 }
305
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700306
307 // Auxiliary delegate to receive notifications about changes applied to
308 // the network configuration - by the apps.
309 private class InternalApplyDelegate implements ConfigApplyDelegate {
310 @Override
311 public void onApply(Config config) {
312 configs.put(key(config.subject(), config.getClass()), config.node());
313 }
314 }
315
316 // Produces a key for uniquely tracking a subject config.
317 private static ConfigKey key(Object subject, Class<?> configClass) {
318 return new ConfigKey(subject, configClass);
319 }
320
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800321 // Produces a key for uniquely tracking a subject config.
322 private static ConfigKey key(Object subject, String configKey) {
323 return new ConfigKey(subject, configKey);
324 }
325
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700326 // Auxiliary key to track subject configurations.
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800327 // Keys with non-null configKey are pending configurations.
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700328 private static final class ConfigKey {
329 final Object subject;
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800330 final String configKey;
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700331 final String configClass;
332
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800333 // Create a key for pending configuration class
334 private ConfigKey(Object subject, String configKey) {
335 this.subject = subject;
336 this.configKey = configKey;
337 this.configClass = null;
338 }
339
340 // Create a key for registered class configuration
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700341 private ConfigKey(Object subject, Class<?> configClass) {
342 this.subject = subject;
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800343 this.configKey = null;
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700344 this.configClass = configClass.getName();
345 }
346
347 @Override
348 public int hashCode() {
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800349 return Objects.hash(subject, configKey, configClass);
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700350 }
351
352 @Override
353 public boolean equals(Object obj) {
354 if (this == obj) {
355 return true;
356 }
357 if (obj instanceof ConfigKey) {
358 final ConfigKey other = (ConfigKey) obj;
359 return Objects.equals(this.subject, other.subject)
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800360 && Objects.equals(this.configKey, other.configKey)
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700361 && Objects.equals(this.configClass, other.configClass);
362 }
363 return false;
364 }
365 }
366
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700367 private class InternalMapListener implements MapEventListener<ConfigKey, JsonNode> {
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700368 @Override
Thomas Vachuska0a400ea2015-09-04 11:25:03 -0700369 public void event(MapEvent<ConfigKey, JsonNode> event) {
Thomas Vachuska6f350ed2016-01-08 09:53:03 -0800370 // Do not delegate pending configs.
371 if (event.key().configClass == null) {
372 return;
373 }
374
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700375 ConfigFactory factory = factoriesByConfig.get(event.key().configClass);
376 if (factory != null) {
Charles Chan023a8982016-02-04 11:00:41 -0800377 Object subject = event.key().subject;
378 Class configClass = factory.configClass();
379 Versioned<JsonNode> newValue = event.newValue();
380 Versioned<JsonNode> oldValue = event.oldValue();
381
382 Config config = (newValue != null) ?
383 createDetachedConfig(subject, configClass, newValue.value()) :
384 null;
385 Config prevConfig = (oldValue != null) ?
386 createDetachedConfig(subject, configClass, oldValue.value()) :
387 null;
388
389 NetworkConfigEvent.Type type;
390 switch (event.type()) {
391 case INSERT:
392 type = CONFIG_ADDED;
393 break;
394 case UPDATE:
395 type = CONFIG_UPDATED;
396 break;
397 case REMOVE:
398 default:
399 type = CONFIG_REMOVED;
400 break;
401 }
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700402 notifyDelegate(new NetworkConfigEvent(type, event.key().subject,
Charles Chan023a8982016-02-04 11:00:41 -0800403 config, prevConfig, factory.configClass()));
Thomas Vachuska96d55b12015-05-11 08:52:03 -0700404 }
405 }
406 }
407}