blob: 9a016cfaa845eddd81ac7ceca845c2a14b7fdf31 [file] [log] [blame]
/*
* Copyright 2015-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.ui.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.DoubleNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.LongNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ShortNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.util.Tools;
import org.onosproject.mastership.MastershipService;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.ConsistentMap;
import org.onosproject.store.service.MapEvent;
import org.onosproject.store.service.MapEventListener;
import org.onosproject.store.service.Serializer;
import org.onosproject.store.service.StorageService;
import org.onosproject.ui.UiExtension;
import org.onosproject.ui.UiExtensionService;
import org.onosproject.ui.UiMessageHandlerFactory;
import org.onosproject.ui.UiPreferencesService;
import org.onosproject.ui.UiSessionToken;
import org.onosproject.ui.UiTokenService;
import org.onosproject.ui.UiTopo2OverlayFactory;
import org.onosproject.ui.UiTopoMap;
import org.onosproject.ui.UiTopoMapFactory;
import org.onosproject.ui.UiTopoOverlayFactory;
import org.onosproject.ui.UiView;
import org.onosproject.ui.UiViewHidden;
import org.onosproject.ui.impl.topo.Topo2TrafficMessageHandler;
import org.onosproject.ui.impl.topo.Topo2ViewMessageHandler;
import org.onosproject.ui.impl.topo.Traffic2Overlay;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static com.google.common.collect.ImmutableList.of;
import static java.util.stream.Collectors.toSet;
import static org.onosproject.security.AppGuard.checkPermission;
import static org.onosproject.security.AppPermission.Type.UI_READ;
import static org.onosproject.security.AppPermission.Type.UI_WRITE;
import static org.onosproject.ui.UiView.Category.NETWORK;
import static org.onosproject.ui.UiView.Category.PLATFORM;
/**
* Manages the user interface extensions.
*/
@Component(immediate = true)
@Service
public class UiExtensionManager
implements UiExtensionService, UiPreferencesService, SpriteService,
UiTokenService {
private static final ClassLoader CL = UiExtensionManager.class.getClassLoader();
private static final String ONOS_USER_PREFERENCES = "onos-ui-user-preferences";
private static final String ONOS_SESSION_TOKENS = "onos-ui-session-tokens";
private static final String CORE = "core";
private static final String GUI_ADDED = "guiAdded";
private static final String GUI_REMOVED = "guiRemoved";
private static final String UPDATE_PREFS = "updatePrefs";
private static final String SLASH = "/";
private static final int IDX_USER = 0;
private static final int IDX_KEY = 1;
private final Logger log = LoggerFactory.getLogger(getClass());
// List of all extensions
private final List<UiExtension> extensions = Lists.newArrayList();
// Map of views to extensions
private final Map<String, UiExtension> views = Maps.newHashMap();
// Core views & core extension
private final UiExtension core = createCoreExtension();
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService mastershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected StorageService storageService;
// User preferences
private ConsistentMap<String, ObjectNode> prefsConsistentMap;
private Map<String, ObjectNode> prefs;
private final MapEventListener<String, ObjectNode> prefsListener =
new InternalPrefsListener();
// Session tokens
private ConsistentMap<UiSessionToken, String> tokensConsistentMap;
private Map<UiSessionToken, String> tokens;
private final SessionTokenGenerator tokenGen =
new SessionTokenGenerator();
private final ObjectMapper mapper = new ObjectMapper();
private final ExecutorService eventHandlingExecutor =
Executors.newSingleThreadExecutor(
Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
// Creates core UI extension
private UiExtension createCoreExtension() {
List<UiView> coreViews = of(
new UiView(PLATFORM, "app", "Applications", "nav_apps"),
new UiView(PLATFORM, "settings", "Settings", "nav_settings"),
new UiView(PLATFORM, "cluster", "Cluster Nodes", "nav_cluster"),
new UiView(PLATFORM, "processor", "Packet Processors", "nav_processors"),
new UiView(PLATFORM, "partition", "Partitions", "nav_partitions"),
new UiView(NETWORK, "topo", "Topology", "nav_topo"),
new UiView(NETWORK, "topo2", "Topology 2", "nav_topo2"),
new UiView(NETWORK, "device", "Devices", "nav_devs"),
new UiViewHidden("flow"),
new UiViewHidden("port"),
new UiViewHidden("group"),
new UiViewHidden("meter"),
new UiView(NETWORK, "link", "Links", "nav_links"),
new UiView(NETWORK, "host", "Hosts", "nav_hosts"),
new UiView(NETWORK, "intent", "Intents", "nav_intents"),
new UiView(NETWORK, "tunnel", "Tunnels", "nav_tunnels")
);
UiMessageHandlerFactory messageHandlerFactory =
() -> ImmutableList.of(
new UserPreferencesMessageHandler(),
new TopologyViewMessageHandler(),
new Topo2ViewMessageHandler(),
new Topo2TrafficMessageHandler(),
new MapSelectorMessageHandler(),
new DeviceViewMessageHandler(),
new LinkViewMessageHandler(),
new HostViewMessageHandler(),
new FlowViewMessageHandler(),
new PortViewMessageHandler(),
new GroupViewMessageHandler(),
new MeterViewMessageHandler(),
new IntentViewMessageHandler(),
new ApplicationViewMessageHandler(),
new SettingsViewMessageHandler(),
new ClusterViewMessageHandler(),
new ProcessorViewMessageHandler(),
new TunnelViewMessageHandler(),
new PartitionViewMessageHandler()
);
UiTopoOverlayFactory topoOverlayFactory =
() -> ImmutableList.of(
new TrafficOverlay(),
new ProtectedIntentOverlay()
);
UiTopo2OverlayFactory topo2OverlayFactory =
() -> ImmutableList.of(
new Traffic2Overlay()
);
UiTopoMapFactory topoMapFactory =
() -> ImmutableList.of(
new UiTopoMap("australia", "Australia", "*australia", 1.0),
new UiTopoMap("americas", "North, Central and South America", "*americas", 0.7),
new UiTopoMap("n_america", "North America", "*n_america", 0.9),
new UiTopoMap("s_america", "South America", "*s_america", 0.9),
new UiTopoMap("usa", "United States", "*continental_us", 1.3),
new UiTopoMap("bayareaGEO", "Bay Area, California", "*bayarea", 1.0),
new UiTopoMap("europe", "Europe", "*europe", 10.0),
new UiTopoMap("italy", "Italy", "*italy", 0.8),
new UiTopoMap("uk", "United Kingdom and Ireland", "*uk", 2.0),
new UiTopoMap("japan", "Japan", "*japan", 0.8),
new UiTopoMap("s_korea", "South Korea", "*s_korea", 0.75),
new UiTopoMap("taiwan", "Taiwan", "*taiwan", 0.7),
new UiTopoMap("africa", "Africa", "*africa", 0.7),
new UiTopoMap("oceania", "Oceania", "*oceania", 0.7),
new UiTopoMap("asia", "Asia", "*asia", 0.7)
);
return new UiExtension.Builder(CL, coreViews)
.messageHandlerFactory(messageHandlerFactory)
.topoOverlayFactory(topoOverlayFactory)
.topo2OverlayFactory(topo2OverlayFactory)
.topoMapFactory(topoMapFactory)
.resourcePath(CORE)
.build();
}
@Activate
public void activate() {
Serializer serializer = Serializer.using(KryoNamespaces.API,
ObjectNode.class, ArrayNode.class,
JsonNodeFactory.class, LinkedHashMap.class,
TextNode.class, BooleanNode.class,
LongNode.class, DoubleNode.class, ShortNode.class,
IntNode.class, NullNode.class, UiSessionToken.class);
prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
.withName(ONOS_USER_PREFERENCES)
.withSerializer(serializer)
.withRelaxedReadConsistency()
.build();
prefsConsistentMap.addListener(prefsListener);
prefs = prefsConsistentMap.asJavaMap();
tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
.withName(ONOS_SESSION_TOKENS)
.withSerializer(serializer)
.withRelaxedReadConsistency()
.build();
tokens = tokensConsistentMap.asJavaMap();
register(core);
log.info("Started");
}
@Deactivate
public void deactivate() {
prefsConsistentMap.removeListener(prefsListener);
eventHandlingExecutor.shutdown();
UiWebSocketServlet.closeAll();
unregister(core);
log.info("Stopped");
}
@Override
public synchronized void register(UiExtension extension) {
checkPermission(UI_WRITE);
if (!extensions.contains(extension)) {
extensions.add(extension);
for (UiView view : extension.views()) {
views.put(view.id(), extension);
}
UiWebSocketServlet.sendToAll(GUI_ADDED, null);
}
}
@Override
public synchronized void unregister(UiExtension extension) {
checkPermission(UI_WRITE);
extensions.remove(extension);
extension.views().stream().map(UiView::id).collect(toSet()).forEach(views::remove);
UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
}
@Override
public synchronized List<UiExtension> getExtensions() {
checkPermission(UI_READ);
return ImmutableList.copyOf(extensions);
}
@Override
public synchronized UiExtension getViewExtension(String viewId) {
checkPermission(UI_READ);
return views.get(viewId);
}
@Override
public Set<String> getUserNames() {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
prefs.keySet().forEach(k -> builder.add(userName(k)));
return builder.build();
}
@Override
public Map<String, ObjectNode> getPreferences(String userName) {
ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
prefs.entrySet().stream()
.filter(e -> e.getKey().startsWith(userName + SLASH))
.forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
return builder.build();
}
@Override
public void setPreference(String userName, String preference, ObjectNode value) {
prefs.put(key(userName, preference), value);
}
// =====================================================================
// Provisional tracking of sprite definitions
private final Map<String, JsonNode> sprites = Maps.newHashMap();
@Override
public Set<String> getNames() {
return ImmutableSet.copyOf(sprites.keySet());
}
@Override
public void put(String name, JsonNode spriteData) {
log.info("Registered sprite definition [{}]", name);
sprites.put(name, spriteData);
}
@Override
public JsonNode get(String name) {
return sprites.get(name);
}
private String key(String userName, String keyName) {
return userName + SLASH + keyName;
}
private String userName(String key) {
return key.split(SLASH)[IDX_USER];
}
private String keyName(String key) {
return key.split(SLASH)[IDX_KEY];
}
// =====================================================================
// UiTokenService
@Override
public UiSessionToken issueToken(String username) {
UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
tokens.put(token, username);
log.debug("UiSessionToken issued: {}", token);
return token;
}
@Override
public void revokeToken(UiSessionToken token) {
if (token != null) {
tokens.remove(token);
log.debug("UiSessionToken revoked: {}", token);
}
}
@Override
public boolean isTokenValid(UiSessionToken token) {
return token != null && tokens.containsKey(token);
}
private final class SessionTokenGenerator {
private final SecureRandom random = new SecureRandom();
/*
This works by choosing 130 bits from a cryptographically secure
random bit generator, and encoding them in base-32.
128 bits is considered to be cryptographically strong, but each
digit in a base 32 number can encode 5 bits, so 128 is rounded up
to the next multiple of 5.
This encoding is compact and efficient, with 5 random bits per
character. Compare this to a random UUID, which only has 3.4 bits
per character in standard layout, and only 122 random bits in total.
Note that SecureRandom objects are expensive to initialize, so
we'll want to keep it around and re-use it.
*/
private String nextSessionId() {
return new BigInteger(130, random).toString(32);
}
}
// Auxiliary listener to preference map events.
private class InternalPrefsListener
implements MapEventListener<String, ObjectNode> {
@Override
public void event(MapEvent<String, ObjectNode> event) {
eventHandlingExecutor.execute(() -> {
String userName = userName(event.key());
if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
}
});
}
private ObjectNode jsonPrefs() {
ObjectNode json = mapper.createObjectNode();
prefs.forEach((key, value) -> json.set(keyName(key), value));
return json;
}
}
}