blob: 1cde0c92d27def510b7dd07276a3c07f7112f10c [file] [log] [blame]
Yuta HIGUCHI4490a732014-11-18 20:20:30 -08001/*
2 * Copyright 2014 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.onlab.onos.store.intent.impl;
17
Yuta HIGUCHIc8f30262014-11-20 19:14:42 -080018import com.google.common.base.Verify;
Yuta HIGUCHI4490a732014-11-18 20:20:30 -080019import com.google.common.collect.ImmutableSet;
20import com.hazelcast.core.EntryAdapter;
21import com.hazelcast.core.EntryEvent;
22import com.hazelcast.core.EntryListener;
23import com.hazelcast.core.IMap;
24import com.hazelcast.core.Member;
25
26import org.apache.felix.scr.annotations.Activate;
27import org.apache.felix.scr.annotations.Component;
28import org.apache.felix.scr.annotations.Deactivate;
29import org.apache.felix.scr.annotations.Service;
30import org.onlab.onos.net.intent.Intent;
31import org.onlab.onos.net.intent.IntentEvent;
32import org.onlab.onos.net.intent.IntentId;
33import org.onlab.onos.net.intent.IntentState;
34import org.onlab.onos.net.intent.IntentStore;
35import org.onlab.onos.net.intent.IntentStoreDelegate;
36import org.onlab.onos.store.hz.AbstractHazelcastStore;
37import org.onlab.onos.store.hz.SMap;
38import org.onlab.onos.store.serializers.KryoNamespaces;
39import org.onlab.onos.store.serializers.KryoSerializer;
40import org.onlab.util.KryoNamespace;
41import org.slf4j.Logger;
42
43import java.util.EnumSet;
44import java.util.List;
45import java.util.Map;
46import java.util.Set;
47import java.util.concurrent.ConcurrentHashMap;
48
Yuta HIGUCHI4490a732014-11-18 20:20:30 -080049import static org.onlab.onos.net.intent.IntentState.*;
50import static org.slf4j.LoggerFactory.getLogger;
51
Yuta HIGUCHIc8f30262014-11-20 19:14:42 -080052@Component(immediate = true, enabled = false)
Yuta HIGUCHI4490a732014-11-18 20:20:30 -080053@Service
54public class HazelcastIntentStore
55 extends AbstractHazelcastStore<IntentEvent, IntentStoreDelegate>
56 implements IntentStore {
57
58 /** Valid parking state, which can transition to INSTALLED. */
Yuta HIGUCHI89a7f472014-11-21 14:50:24 -080059 private static final Set<IntentState> PRE_INSTALLED = EnumSet.of(SUBMITTED, INSTALLED, FAILED);
Yuta HIGUCHI4490a732014-11-18 20:20:30 -080060
61 /** Valid parking state, which can transition to WITHDRAWN. */
62 private static final Set<IntentState> PRE_WITHDRAWN = EnumSet.of(INSTALLED, FAILED);
63
64 private final Logger log = getLogger(getClass());
65
66 // Assumption: IntentId will not have synonyms
67 private SMap<IntentId, Intent> intents;
68 private SMap<IntentId, IntentState> states;
69
70 // Map to store instance local intermediate state transition
71 private transient Map<IntentId, IntentState> transientStates = new ConcurrentHashMap<>();
72
73 private SMap<IntentId, List<Intent>> installable;
74
Yuta HIGUCHIc8f30262014-11-20 19:14:42 -080075 // TODO make this configurable
76 private boolean onlyLogTransitionError = true;
77
Yuta HIGUCHI4490a732014-11-18 20:20:30 -080078 @Override
79 @Activate
80 public void activate() {
81 // FIXME: We need a way to add serializer for intents which has been plugged-in.
82 // As a short term workaround, relax Kryo config to
83 // registrationRequired=false
84 super.activate();
85 super.serializer = new KryoSerializer() {
86
87 @Override
88 protected void setupKryoPool() {
89 serializerPool = KryoNamespace.newBuilder()
90 .setRegistrationRequired(false)
91 .register(KryoNamespaces.API)
Yuta HIGUCHI91768e32014-11-22 05:06:35 -080092 .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID)
93 .build();
Yuta HIGUCHI4490a732014-11-18 20:20:30 -080094 }
95
96 };
97
98 // TODO: enable near cache, allow read from backup for this IMap
99 IMap<byte[], byte[]> rawIntents = super.theInstance.getMap("intents");
100 intents = new SMap<>(rawIntents , super.serializer);
101
102 // TODO: disable near cache, disable read from backup for this IMap
103 IMap<byte[], byte[]> rawStates = super.theInstance.getMap("intent-states");
104 states = new SMap<>(rawStates , super.serializer);
105 EntryListener<IntentId, IntentState> listener = new RemoteIntentStateListener();
106 states.addEntryListener(listener , false);
107
108 transientStates.clear();
109
110 // TODO: disable near cache, disable read from backup for this IMap
111 IMap<byte[], byte[]> rawInstallables = super.theInstance.getMap("installable-intents");
112 installable = new SMap<>(rawInstallables , super.serializer);
113
114 log.info("Started");
115 }
116
117 @Deactivate
118 public void deactivate() {
119 log.info("Stopped");
120 }
121
122 @Override
123 public IntentEvent createIntent(Intent intent) {
124 Intent existing = intents.putIfAbsent(intent.id(), intent);
125 if (existing != null) {
126 // duplicate, ignore
127 return null;
128 } else {
129 return this.setState(intent, IntentState.SUBMITTED);
130 }
131 }
132
133 @Override
134 public IntentEvent removeIntent(IntentId intentId) {
135 Intent intent = intents.remove(intentId);
136 installable.remove(intentId);
137 if (intent == null) {
138 // was already removed
139 return null;
140 }
141 IntentEvent event = this.setState(intent, WITHDRAWN);
142 states.remove(intentId);
143 transientStates.remove(intentId);
144 // TODO: Should we callremoveInstalledIntents if this Intent was
145 return event;
146 }
147
148 @Override
149 public long getIntentCount() {
150 return intents.size();
151 }
152
153 @Override
154 public Iterable<Intent> getIntents() {
155 return ImmutableSet.copyOf(intents.values());
156 }
157
158 @Override
159 public Intent getIntent(IntentId intentId) {
160 return intents.get(intentId);
161 }
162
163 @Override
164 public IntentState getIntentState(IntentId id) {
165 final IntentState localState = transientStates.get(id);
166 if (localState != null) {
167 return localState;
168 }
169 return states.get(id);
170 }
171
Yuta HIGUCHIc8f30262014-11-20 19:14:42 -0800172 private void verify(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) {
173 if (onlyLogTransitionError) {
174 if (!expression) {
175 log.error(errorMessageTemplate.replace("%s", "{}"), errorMessageArgs);
176 }
177 } else {
178 Verify.verify(expression, errorMessageTemplate, errorMessageArgs);
179 }
180 }
Yuta HIGUCHI4490a732014-11-18 20:20:30 -0800181
182 @Override
183 public IntentEvent setState(Intent intent, IntentState state) {
184 final IntentId id = intent.id();
185 IntentEvent.Type type = null;
186 final IntentState prevParking;
187 boolean transientStateChangeOnly = false;
188
189 // parking state transition
190 switch (state) {
191 case SUBMITTED:
Yuta HIGUCHI89a7f472014-11-21 14:50:24 -0800192 prevParking = states.get(id);
193 if (prevParking == null) {
194 IntentState existing = states.putIfAbsent(id, SUBMITTED);
Brian O'Connor895c12a2014-11-23 19:21:46 -0800195 verify(existing == null, "Conditional replace %s => %s failed", prevParking, SUBMITTED);
Yuta HIGUCHI89a7f472014-11-21 14:50:24 -0800196 } else {
197 verify(prevParking == WITHDRAWN,
198 "Illegal state transition attempted from %s to SUBMITTED",
199 prevParking);
200 boolean updated = states.replace(id, prevParking, SUBMITTED);
201 verify(updated, "Conditional replace %s => %s failed", prevParking, SUBMITTED);
202 }
Yuta HIGUCHI4490a732014-11-18 20:20:30 -0800203 type = IntentEvent.Type.SUBMITTED;
204 break;
205 case INSTALLED:
206 prevParking = states.replace(id, INSTALLED);
207 verify(PRE_INSTALLED.contains(prevParking),
208 "Illegal state transition attempted from %s to INSTALLED",
209 prevParking);
210 type = IntentEvent.Type.INSTALLED;
211 break;
212 case FAILED:
213 prevParking = states.replace(id, FAILED);
214 type = IntentEvent.Type.FAILED;
215 break;
216 case WITHDRAWN:
217 prevParking = states.replace(id, WITHDRAWN);
218 verify(PRE_WITHDRAWN.contains(prevParking),
219 "Illegal state transition attempted from %s to WITHDRAWN",
220 prevParking);
221 type = IntentEvent.Type.WITHDRAWN;
222 break;
223 default:
224 transientStateChangeOnly = true;
225 prevParking = null;
226 break;
227 }
228 if (!transientStateChangeOnly) {
229 log.debug("Parking State change: {} {}=>{}", id, prevParking, state);
230 }
231 // Update instance local state, which includes non-parking state transition
232 final IntentState prevTransient = transientStates.put(id, state);
233 log.debug("Transient State change: {} {}=>{}", id, prevTransient, state);
234
235 if (type == null) {
236 return null;
237 }
238 return new IntentEvent(type, intent);
239 }
240
241 @Override
242 public void setInstallableIntents(IntentId intentId, List<Intent> result) {
243 installable.put(intentId, result);
244 }
245
246 @Override
247 public List<Intent> getInstallableIntents(IntentId intentId) {
248 return installable.get(intentId);
249 }
250
251 @Override
252 public void removeInstalledIntents(IntentId intentId) {
253 installable.remove(intentId);
254 }
255
256 public final class RemoteIntentStateListener extends EntryAdapter<IntentId, IntentState> {
257
258 @Override
259 public void onEntryEvent(EntryEvent<IntentId, IntentState> event) {
260 final Member myself = theInstance.getCluster().getLocalMember();
261 if (!myself.equals(event.getMember())) {
262 // When Intent state was modified by remote node,
263 // clear local transient state.
264 final IntentId intentId = event.getKey();
265 IntentState oldState = transientStates.remove(intentId);
266 if (oldState != null) {
267 log.debug("{} state updated remotely, removing transient state {}",
268 intentId, oldState);
269 }
270 }
271 }
272 }
273}