blob: bcdb93929acb174f81791a925f9f13cc3939da2f [file] [log] [blame]
Carmelo Cascone17fc9e42016-05-31 11:29:21 -07001/*
2 * Copyright 2016-present 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 */
16
17package org.onosproject.bmv2.ctl;
18
19import com.eclipsesource.json.Json;
20import com.eclipsesource.json.JsonObject;
21import com.esotericsoftware.kryo.Kryo;
22import com.esotericsoftware.kryo.io.Input;
23import com.esotericsoftware.kryo.io.Output;
24import com.google.common.collect.Maps;
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.Reference;
29import org.apache.felix.scr.annotations.ReferenceCardinality;
30import org.apache.felix.scr.annotations.Service;
31import org.onlab.util.KryoNamespace;
32import org.onlab.util.SharedExecutors;
33import org.onosproject.bmv2.api.context.Bmv2Configuration;
34import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
35import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
36import org.onosproject.bmv2.api.context.Bmv2Interpreter;
37import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
38import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
39import org.onosproject.bmv2.api.service.Bmv2Controller;
40import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
41import org.onosproject.net.DeviceId;
42import org.onosproject.store.serializers.KryoNamespaces;
43import org.onosproject.store.service.ConsistentMap;
44import org.onosproject.store.service.Serializer;
45import org.onosproject.store.service.StorageService;
46import org.slf4j.Logger;
47import org.slf4j.LoggerFactory;
48
49import java.io.BufferedReader;
50import java.io.IOException;
51import java.io.InputStreamReader;
52import java.util.Map;
53import java.util.concurrent.ExecutorService;
54
55import static com.google.common.base.Preconditions.checkNotNull;
56
57@Component(immediate = true)
58@Service
59public class Bmv2DeviceContextServiceImpl implements Bmv2DeviceContextService {
60
61 private static final String JSON_DEFAULT_CONFIG_PATH = "/default.json";
62
63 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
64 private StorageService storageService;
65
66 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
67 private Bmv2Controller controller;
68
69 private final ExecutorService executorService = SharedExecutors.getPoolThreadExecutor();
70
71 private ConsistentMap<DeviceId, Bmv2DeviceContext> contexts;
72 private Map<DeviceId, Bmv2DeviceContext> contextsMap;
73
74 private Map<String, ClassLoader> interpreterClassLoaders;
75
76 private Bmv2DeviceContext defaultContext;
77
78 private final Logger log = LoggerFactory.getLogger(getClass());
79
80 @Activate
81 public void activate() {
82 KryoNamespace kryo = new KryoNamespace.Builder()
83 .register(KryoNamespaces.API)
84 .register(new BmvDeviceContextSerializer(), Bmv2DeviceContext.class)
85 .build();
86
87 this.contexts = storageService.<DeviceId, Bmv2DeviceContext>consistentMapBuilder()
88 .withSerializer(Serializer.using(kryo))
89 .withName("onos-bmv2-contexts")
90 .build();
91 contextsMap = contexts.asJavaMap();
92
93 interpreterClassLoaders = Maps.newConcurrentMap();
94
95 Bmv2Configuration defaultConfiguration = loadDefaultConfiguration();
96 Bmv2Interpreter defaultInterpreter = new Bmv2DefaultInterpreterImpl();
97 defaultContext = new Bmv2DeviceContext(defaultConfiguration, defaultInterpreter);
98
99 interpreterClassLoaders.put(defaultInterpreter.getClass().getName(), this.getClass().getClassLoader());
100
101 log.info("Started");
102 }
103
104 @Deactivate
105 public void deactivate() {
106 log.info("Stopped");
107 }
108
109 @Override
110 public Bmv2DeviceContext getContext(DeviceId deviceId) {
111 checkNotNull(deviceId, "device id cannot be null");
112 return contextsMap.get(deviceId);
113 }
114
115 @Override
116 public void triggerConfigurationSwap(DeviceId deviceId, Bmv2DeviceContext context) {
117 checkNotNull(deviceId, "device id cannot be null");
118 checkNotNull(context, "context cannot be null");
119 if (!interpreterClassLoaders.containsKey(context.interpreter().getClass().getName())) {
120 log.error("Unable to trigger configuration swap, missing class loader for context interpreter. " +
121 "Please register it with registerInterpreterClassLoader()");
122 } else {
123 executorService.execute(() -> executeConfigurationSwap(deviceId, context));
124 }
125 }
126
127 @Override
128 public void registerInterpreterClassLoader(Class<? extends Bmv2Interpreter> interpreterClass, ClassLoader loader) {
129 interpreterClassLoaders.put(interpreterClass.getName(), loader);
130 }
131
132 private void executeConfigurationSwap(DeviceId deviceId, Bmv2DeviceContext context) {
133 contexts.compute(deviceId, (key, existingValue) -> {
134 if (context.equals(existingValue)) {
135 log.info("Dropping swap request as one has already been triggered for the given context.");
136 return existingValue;
137 }
138 try {
139 Bmv2DeviceAgent agent = controller.getAgent(deviceId);
140 String jsonString = context.configuration().json().toString();
141 agent.loadNewJsonConfig(jsonString);
142 agent.swapJsonConfig();
143 return context;
144 } catch (Bmv2RuntimeException e) {
145 log.error("Unable to swap configuration on {}: {}", deviceId, e.explain());
146 return existingValue;
147 }
148 });
149 }
150
151 @Override
152 public boolean notifyDeviceChange(DeviceId deviceId) {
153 checkNotNull(deviceId, "device id cannot be null");
154
155 Bmv2DeviceContext storedContext = getContext(deviceId);
156
157 if (storedContext == null) {
158 log.info("No context previously stored for {}, swapping to DEFAULT_CONTEXT.", deviceId);
159 triggerConfigurationSwap(deviceId, defaultContext);
160 // Device can be accepted.
161 return false;
162 } else {
163 Bmv2Configuration deviceConfiguration = loadDeviceConfiguration(deviceId);
164 if (deviceConfiguration == null) {
165 log.warn("Unable to load configuration from device {}", deviceId);
166 return false;
167 }
168 if (storedContext.configuration().equals(deviceConfiguration)) {
169 return true;
170 } else {
171 log.info("Device context is different from the stored one, triggering configuration swap for {}...",
172 deviceId);
173 triggerConfigurationSwap(deviceId, storedContext);
174 return false;
175 }
176 }
177 }
178
179 /**
180 * Load and parse a BMv2 JSON configuration from the given device.
181 *
182 * @param deviceId a device id
183 * @return a BMv2 configuration
184 */
185 private Bmv2Configuration loadDeviceConfiguration(DeviceId deviceId) {
186 try {
187 String jsonString = controller.getAgent(deviceId).dumpJsonConfig();
188 return Bmv2DefaultConfiguration.parse(Json.parse(jsonString).asObject());
189 } catch (Bmv2RuntimeException e) {
190 log.warn("Unable to load JSON configuration from {}: {}", deviceId, e.explain());
191 return null;
192 }
193 }
194
195 /**
196 * Loads default configuration from file.
197 *
198 * @return a BMv2 configuration
199 */
200 protected static Bmv2DefaultConfiguration loadDefaultConfiguration() {
201 try {
202 JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
203 Bmv2DeviceContextServiceImpl.class.getResourceAsStream(JSON_DEFAULT_CONFIG_PATH)))).asObject();
204 return Bmv2DefaultConfiguration.parse(json);
205 } catch (IOException e) {
206 throw new RuntimeException("Unable to load default configuration", e);
207 }
208 }
209
210 /**
211 * Internal BMv2 context serializer.
212 */
213 private class BmvDeviceContextSerializer extends com.esotericsoftware.kryo.Serializer<Bmv2DeviceContext> {
214
215 @Override
216 public void write(Kryo kryo, Output output, Bmv2DeviceContext context) {
217 kryo.writeObject(output, context.configuration().json().toString());
218 kryo.writeObject(output, context.interpreter().getClass().getName());
219 }
220
221 @Override
222 public Bmv2DeviceContext read(Kryo kryo, Input input, Class<Bmv2DeviceContext> type) {
223 String jsonStr = kryo.readObject(input, String.class);
224 String interpreterClassName = kryo.readObject(input, String.class);
225 Bmv2Configuration configuration = Bmv2DefaultConfiguration.parse(Json.parse(jsonStr).asObject());
226 ClassLoader loader = interpreterClassLoaders.get(interpreterClassName);
227 if (loader == null) {
228 throw new IllegalStateException("No class loader registered for interpreter: " + interpreterClassName);
229 }
230 try {
231 Bmv2Interpreter interpreter = (Bmv2Interpreter) loader.loadClass(interpreterClassName).newInstance();
232 return new Bmv2DeviceContext(configuration, interpreter);
233 } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
234 throw new RuntimeException("Unable to load interpreter class", e);
235 }
236 }
237 }
238}