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