2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.controller.topologymanager.internal;
11 import java.io.FileNotFoundException;
12 import java.io.IOException;
13 import java.io.ObjectInputStream;
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.Dictionary;
17 import java.util.EnumSet;
18 import java.util.HashMap;
19 import java.util.HashSet;
20 import java.util.Iterator;
21 import java.util.List;
24 import java.util.concurrent.ConcurrentHashMap;
25 import java.util.concurrent.ConcurrentMap;
27 import org.apache.commons.lang3.tuple.ImmutablePair;
28 import org.apache.felix.dm.Component;
29 import org.eclipse.osgi.framework.console.CommandInterpreter;
30 import org.eclipse.osgi.framework.console.CommandProvider;
31 import org.opendaylight.controller.clustering.services.CacheConfigException;
32 import org.opendaylight.controller.clustering.services.CacheExistException;
33 import org.opendaylight.controller.clustering.services.IClusterContainerServices;
34 import org.opendaylight.controller.clustering.services.IClusterServices;
35 import org.opendaylight.controller.configuration.IConfigurationContainerAware;
36 import org.opendaylight.controller.sal.core.Edge;
37 import org.opendaylight.controller.sal.core.Host;
38 import org.opendaylight.controller.sal.core.Node;
39 import org.opendaylight.controller.sal.core.NodeConnector;
40 import org.opendaylight.controller.sal.core.Property;
41 import org.opendaylight.controller.sal.core.TimeStamp;
42 import org.opendaylight.controller.sal.core.UpdateType;
43 import org.opendaylight.controller.sal.topology.IListenTopoUpdates;
44 import org.opendaylight.controller.sal.topology.ITopologyService;
45 import org.opendaylight.controller.sal.topology.TopoEdgeUpdate;
46 import org.opendaylight.controller.sal.utils.GlobalConstants;
47 import org.opendaylight.controller.sal.utils.IObjectReader;
48 import org.opendaylight.controller.sal.utils.ObjectReader;
49 import org.opendaylight.controller.sal.utils.ObjectWriter;
50 import org.opendaylight.controller.sal.utils.Status;
51 import org.opendaylight.controller.sal.utils.StatusCode;
52 import org.opendaylight.controller.topologymanager.ITopologyManager;
53 import org.opendaylight.controller.topologymanager.ITopologyManagerAware;
54 import org.opendaylight.controller.topologymanager.TopologyUserLinkConfig;
55 import org.osgi.framework.BundleContext;
56 import org.osgi.framework.FrameworkUtil;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
61 * The class describes TopologyManager which is the central repository of the
62 * network topology. It provides service for applications to interact with
63 * topology database and notifies all the listeners of topology changes.
65 public class TopologyManagerImpl implements ITopologyManager,
66 IConfigurationContainerAware, IListenTopoUpdates, IObjectReader,
68 private static final Logger log = LoggerFactory
69 .getLogger(TopologyManagerImpl.class);
70 private ITopologyService topoService = null;
71 private IClusterContainerServices clusterContainerService = null;
72 // DB of all the Edges with properties which constitute our topology
73 private ConcurrentMap<Edge, Set<Property>> edgesDB = null;
74 // DB of all NodeConnector which are part of ISL Edges, meaning they
75 // are connected to another NodeConnector on the other side of an ISL link.
76 // NodeConnector of a Production Edge is not part of this DB.
77 private ConcurrentMap<NodeConnector, Set<Property>> nodeConnectorsDB = null;
78 // DB of all the NodeConnectors with an Host attached to it
79 private ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>> hostsDB = null;
80 // Topology Manager Aware listeners
81 private Set<ITopologyManagerAware> topologyManagerAware = Collections
82 .synchronizedSet(new HashSet<ITopologyManagerAware>());
84 private static String ROOT = GlobalConstants.STARTUPHOME.toString();
85 private String userLinksFileName = null;
86 private ConcurrentMap<String, TopologyUserLinkConfig> userLinks;
88 void nonClusterObjectCreate() {
89 edgesDB = new ConcurrentHashMap<Edge, Set<Property>>();
90 hostsDB = new ConcurrentHashMap<NodeConnector, ImmutablePair<Host, Set<Property>>>();
91 userLinks = new ConcurrentHashMap<String, TopologyUserLinkConfig>();
92 nodeConnectorsDB = new ConcurrentHashMap<NodeConnector, Set<Property>>();
95 void setTopologyManagerAware(ITopologyManagerAware s) {
96 if (this.topologyManagerAware != null) {
97 log.debug("Adding ITopologyManagerAware: {}", s);
98 this.topologyManagerAware.add(s);
102 void unsetTopologyManagerAware(ITopologyManagerAware s) {
103 if (this.topologyManagerAware != null) {
104 log.debug("Removing ITopologyManagerAware: {}", s);
105 this.topologyManagerAware.remove(s);
109 void setTopoService(ITopologyService s) {
110 log.debug("Adding ITopologyService: {}", s);
111 this.topoService = s;
114 void unsetTopoService(ITopologyService s) {
115 if (this.topoService == s) {
116 log.debug("Removing ITopologyService: {}", s);
117 this.topoService = null;
121 void setClusterContainerService(IClusterContainerServices s) {
122 log.debug("Cluster Service set");
123 this.clusterContainerService = s;
126 void unsetClusterContainerService(IClusterContainerServices s) {
127 if (this.clusterContainerService == s) {
128 log.debug("Cluster Service removed!");
129 this.clusterContainerService = null;
134 * Function called by the dependency manager when all the required
135 * dependencies are satisfied
138 void init(Component c) {
139 String containerName = null;
140 Dictionary props = c.getServiceProperties();
142 containerName = (String) props.get("containerName");
144 // In the Global instance case the containerName is empty
145 containerName = "UNKNOWN";
148 if (this.clusterContainerService == null) {
149 log.error("Cluster Services is null, not expected!");
153 if (this.topoService == null) {
154 log.error("Topology Services is null, not expected!");
159 this.edgesDB = (ConcurrentMap<Edge, Set<Property>>) this.clusterContainerService
160 .createCache("topologymanager.edgesDB", EnumSet
161 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
162 } catch (CacheExistException cee) {
163 log.error("topologymanager.edgesDB Cache already exists - "
164 + "destroy and recreate if needed");
165 } catch (CacheConfigException cce) {
166 log.error("topologymanager.edgesDB Cache configuration invalid - "
167 + "check cache mode");
171 this.hostsDB = (ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>>) this.clusterContainerService
172 .createCache("topologymanager.hostsDB", EnumSet
173 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
174 } catch (CacheExistException cee) {
175 log.error("topologymanager.hostsDB Cache already exists - "
176 + "destroy and recreate if needed");
177 } catch (CacheConfigException cce) {
178 log.error("topologymanager.hostsDB Cache configuration invalid - "
179 + "check cache mode");
183 this.nodeConnectorsDB = (ConcurrentMap<NodeConnector, Set<Property>>) this.clusterContainerService
184 .createCache("topologymanager.nodeConnectorDB", EnumSet
185 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
186 } catch (CacheExistException cee) {
187 log.error("topologymanager.nodeConnectorDB Cache already exists"
188 + " - destroy and recreate if needed");
189 } catch (CacheConfigException cce) {
190 log.error("topologymanager.nodeConnectorDB Cache configuration "
191 + "invalid - check cache mode");
194 userLinks = new ConcurrentHashMap<String, TopologyUserLinkConfig>();
196 userLinksFileName = ROOT + "userTopology_" + containerName + ".conf";
197 registerWithOSGIConsole();
202 * Function called after the topology manager has registered the service in
203 * OSGi service registry.
207 // SollicitRefresh MUST be called here else if called at init
208 // time it may sollicit refresh too soon.
209 log.debug("Sollicit topology refresh");
210 topoService.sollicitRefresh();
214 * Function called by the dependency manager when at least one dependency
215 * become unsatisfied or when the component is shutting down because for
216 * example bundle is being stopped.
220 if (this.clusterContainerService == null) {
221 log.error("Cluster Services is null, not expected!");
224 this.nodeConnectorsDB = null;
227 this.clusterContainerService.destroyCache("topologymanager.edgesDB");
229 this.clusterContainerService.destroyCache("topologymanager.hostsDB");
231 this.clusterContainerService
232 .destroyCache("topologymanager.nodeConnectorDB");
233 this.nodeConnectorsDB = null;
234 log.debug("Topology Manager DB Deallocated");
237 @SuppressWarnings("unchecked")
238 private void loadConfiguration() {
239 ObjectReader objReader = new ObjectReader();
240 ConcurrentMap<String, TopologyUserLinkConfig> confList = (ConcurrentMap<String, TopologyUserLinkConfig>) objReader
241 .read(this, userLinksFileName);
243 if (confList == null) {
247 for (TopologyUserLinkConfig conf : confList.values()) {
253 public Status saveConfig() {
254 // Publish the save config event to the cluster nodes
256 * Get the CLUSTERING SERVICES WORKING BEFORE TRYING THIS
258 * configSaveEvent.put(new Date().getTime(), SAVE);
260 return saveConfigInternal();
263 public Status saveConfigInternal() {
265 ObjectWriter objWriter = new ObjectWriter();
268 .write(new ConcurrentHashMap<String, TopologyUserLinkConfig>(
269 userLinks), userLinksFileName);
271 if (retS.isSuccess()) {
274 return new Status(StatusCode.INTERNALERROR, "Save failed");
279 public Map<Node, Set<Edge>> getNodeEdges() {
280 if (this.edgesDB == null) {
284 HashMap<Node, Set<Edge>> res = new HashMap<Node, Set<Edge>>();
285 for (Edge key : this.edgesDB.keySet()) {
286 // Lets analyze the tail
287 Node node = key.getTailNodeConnector().getNode();
288 Set<Edge> nodeEdges = res.get(node);
289 if (nodeEdges == null) {
290 nodeEdges = new HashSet<Edge>();
293 // We need to re-add to the MAP even if the element was
294 // already there so in case of clustered services the map
295 // gets updated in the cluster
296 res.put(node, nodeEdges);
298 // Lets analyze the head
299 node = key.getHeadNodeConnector().getNode();
300 nodeEdges = res.get(node);
301 if (nodeEdges == null) {
302 nodeEdges = new HashSet<Edge>();
305 // We need to re-add to the MAP even if the element was
306 // already there so in case of clustered services the map
307 // gets updated in the cluster
308 res.put(node, nodeEdges);
315 public boolean isInternal(NodeConnector p) {
316 if (this.nodeConnectorsDB == null) {
320 // This is an internal NodeConnector if is connected to
321 // another Node i.e it's part of the nodeConnectorsDB
322 return (this.nodeConnectorsDB.get(p) != null);
326 * This method returns true if the edge is an ISL link.
330 * @return true if it is an ISL link
332 public boolean isISLink(Edge e) {
333 return (!isProductionLink(e));
337 * This method returns true if the edge is a production link.
341 * @return true if it is a production link
343 public boolean isProductionLink(Edge e) {
344 return (e.getHeadNodeConnector().getType()
345 .equals(NodeConnector.NodeConnectorIDType.PRODUCTION) || e
346 .getTailNodeConnector().getType()
347 .equals(NodeConnector.NodeConnectorIDType.PRODUCTION));
351 * The Map returned is a copy of the current topology hence if the topology
352 * changes the copy doesn't
354 * @return A Map representing the current topology expressed as edges of the
358 public Map<Edge, Set<Property>> getEdges() {
359 if (this.edgesDB == null) {
363 HashMap<Edge, Set<Property>> res = new HashMap<Edge, Set<Property>>();
364 for (Edge key : this.edgesDB.keySet()) {
365 // Sets of props are copied because the composition of
366 // those properties could change with time
367 HashSet<Property> prop = new HashSet<Property>(
368 this.edgesDB.get(key));
369 // We can simply reuse the key because the object is
370 // immutable so doesn't really matter that we are
371 // referencing the only owned by a different table, the
372 // meaning is the same because doesn't change with time.
379 // TODO remove with spring-dm removal
382 * the topologyAware to set
384 public void setTopologyAware(Set<Object> set) {
385 for (Object s : set) {
386 setTopologyManagerAware((ITopologyManagerAware) s);
391 public Set<NodeConnector> getNodeConnectorWithHost() {
392 if (this.hostsDB == null) {
396 return (this.hostsDB.keySet());
400 public Map<Node, Set<NodeConnector>> getNodesWithNodeConnectorHost() {
401 if (this.hostsDB == null) {
404 HashMap<Node, Set<NodeConnector>> res = new HashMap<Node, Set<NodeConnector>>();
406 for (NodeConnector p : this.hostsDB.keySet()) {
407 Node n = p.getNode();
408 Set<NodeConnector> pSet = res.get(n);
410 // Create the HashSet if null
411 pSet = new HashSet<NodeConnector>();
415 // Keep updating the HashSet, given this is not a
416 // clustered map we can just update the set without
417 // worrying to update the hashmap.
425 public Host getHostAttachedToNodeConnector(NodeConnector p) {
426 if (this.hostsDB == null) {
429 if (this.hostsDB.get(p) == null)
432 return (this.hostsDB.get(p).getLeft());
436 public void updateHostLink(NodeConnector p, Host h, UpdateType t,
437 Set<Property> props) {
438 if (this.hostsDB == null) {
445 // Clone the property set in case non null else just
446 // create an empty one. Caches allocated via infinispan
447 // don't allow null values
449 props = new HashSet<Property>();
451 props = new HashSet<Property>(props);
454 this.hostsDB.put(p, new ImmutablePair(h, props));
457 this.hostsDB.remove(p);
462 private TopoEdgeUpdate edgeUpdate(Edge e, UpdateType type,
463 Set<Property> props) {
466 // Make sure the props are non-null
468 props = (Set<Property>) new HashSet();
470 // Copy the set so noone is going to change the content
471 props = (Set<Property>) new HashSet(props);
474 // Now make sure there is the creation timestamp for the
475 // edge, if not there timestamp with the first update
476 boolean found_create = false;
477 for (Property prop : props) {
478 if (prop instanceof TimeStamp) {
479 TimeStamp t = (TimeStamp) prop;
480 if (t.getTimeStampName().equals("creation")) {
487 TimeStamp t = new TimeStamp(System.currentTimeMillis(),
492 // Now add this in the database eventually overriding
493 // something that may have been already existing
494 this.edgesDB.put(e, props);
496 // Now populate the DB of NodeConnectors
497 // NOTE WELL: properties are empty sets, not really needed
499 // The DB only contains ISL ports
501 this.nodeConnectorsDB.put(e.getHeadNodeConnector(),
502 new HashSet<Property>());
503 this.nodeConnectorsDB.put(e.getTailNodeConnector(),
504 new HashSet<Property>());
506 log.trace("Edge {} {}", e.toString(), type.name());
509 // Now remove the edge from edgesDB
510 this.edgesDB.remove(e);
512 // Now lets update the NodeConnectors DB, the assumption
513 // here is that two NodeConnector are exclusively
514 // connected by 1 and only 1 edge, this is reasonable in
515 // the same plug (virtual of phisical) we can assume two
516 // cables won't be plugged. This could break only in case
517 // of devices in the middle that acts as hubs, but it
518 // should be safe to assume that won't happen.
519 this.nodeConnectorsDB.remove(e.getHeadNodeConnector());
520 this.nodeConnectorsDB.remove(e.getTailNodeConnector());
521 log.trace("Edge {} {}", e.toString(), type.name());
524 Set<Property> old_props = this.edgesDB.get(e);
526 // When property changes lets make sure we can change it
527 // all except the creation time stamp because that should
528 // be changed only when the edge is destroyed and created
531 for (Property prop : old_props) {
532 if (prop instanceof TimeStamp) {
533 TimeStamp t = (TimeStamp) prop;
534 if (t.getTimeStampName().equals("creation")) {
540 // Now lets make sure new properties are non-null
541 // Make sure the props are non-null
543 props = (Set<Property>) new HashSet();
545 // Copy the set so noone is going to change the content
546 props = (Set<Property>) new HashSet(props);
549 // Now lets remove the creation property if exist in the
551 for (Iterator<Property> i = props.iterator(); i.hasNext();) {
552 Property prop = i.next();
553 if (prop instanceof TimeStamp) {
554 TimeStamp t = (TimeStamp) prop;
555 if (t.getTimeStampName().equals("creation")) {
561 // Now lets add the creation timestamp in it
567 this.edgesDB.put(e, props);
568 log.trace("Edge {} {}", e.toString(), type.name());
571 return new TopoEdgeUpdate(e, props, type);
575 public void edgeUpdate(List<TopoEdgeUpdate> topoedgeupdateList) {
576 List<TopoEdgeUpdate> teuList = new ArrayList<TopoEdgeUpdate>();
577 for (int i = 0; i < topoedgeupdateList.size(); i++) {
578 Edge e = topoedgeupdateList.get(i).getEdge();
579 Set<Property> p = topoedgeupdateList.get(i).getProperty();
580 UpdateType type = topoedgeupdateList.get(i).getUpdateType();
581 TopoEdgeUpdate teu = edgeUpdate(e, type, p);
585 // Now update the listeners
586 for (ITopologyManagerAware s : this.topologyManagerAware) {
588 s.edgeUpdate(teuList);
589 } catch (Exception exc) {
590 log.error("Exception on callback", exc);
596 private Edge getReverseLinkTuple(TopologyUserLinkConfig link) {
597 TopologyUserLinkConfig rLink = new TopologyUserLinkConfig(
598 link.getName(), link.getDstNodeConnector(), link.getSrcNodeConnector());
599 return getLinkTuple(rLink);
603 private Edge getLinkTuple(TopologyUserLinkConfig link) {
604 Edge linkTuple = null;
605 NodeConnector srcNodeConnector = NodeConnector.fromString(link.getSrcNodeConnector());
606 NodeConnector dstNodeConnector = NodeConnector.fromString(link.getDstNodeConnector());
607 if (srcNodeConnector == null || dstNodeConnector == null) return null;
609 linkTuple = new Edge(srcNodeConnector, dstNodeConnector);
610 } catch (Exception e) {
616 public ConcurrentMap<String, TopologyUserLinkConfig> getUserLinks() {
621 public Status addUserLink(TopologyUserLinkConfig link) {
622 if (!link.isValid()) {
623 return new Status(StatusCode.BADREQUEST,
624 "Configuration Invalid. Please check the parameters");
626 if (userLinks.get(link.getName()) != null) {
627 return new Status(StatusCode.CONFLICT, "Link with name : "
629 + " already exists. Please use another name");
631 if (userLinks.containsValue(link)) {
632 return new Status(StatusCode.CONFLICT, "Link configuration exists");
635 link.setStatus(TopologyUserLinkConfig.STATUS.LINKDOWN);
636 userLinks.put(link.getName(), link);
638 Edge linkTuple = getLinkTuple(link);
639 if (linkTuple != null) {
640 if (!isProductionLink(linkTuple)) {
641 edgeUpdate(linkTuple, UpdateType.ADDED, new HashSet<Property>());
644 linkTuple = getReverseLinkTuple(link);
645 if (linkTuple != null) {
646 link.setStatus(TopologyUserLinkConfig.STATUS.SUCCESS);
647 if (!isProductionLink(linkTuple)) {
648 edgeUpdate(linkTuple, UpdateType.ADDED, new HashSet<Property>());
652 return new Status(StatusCode.SUCCESS, null);
656 public Status deleteUserLink(String linkName) {
657 if (linkName == null) {
658 return new Status(StatusCode.BADREQUEST,
659 "A valid linkName is required to Delete a link");
662 TopologyUserLinkConfig link = userLinks.get(linkName);
664 Edge linkTuple = getLinkTuple(link);
665 userLinks.remove(linkName);
666 if (linkTuple != null) {
667 if (!isProductionLink(linkTuple)) {
668 edgeUpdate(linkTuple, UpdateType.REMOVED, null);
671 linkTuple = getReverseLinkTuple(link);
672 if ((linkTuple != null) && !isProductionLink(linkTuple)) {
673 edgeUpdate(linkTuple, UpdateType.REMOVED, null);
676 return new Status(StatusCode.SUCCESS, null);
679 private void registerWithOSGIConsole() {
680 BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
682 bundleContext.registerService(CommandProvider.class.getName(), this,
687 public String getHelp() {
688 StringBuffer help = new StringBuffer();
689 help.append("---Topology Manager---\n");
690 help.append("\t addUserLink <name> <node connector string> <node connector string>\n");
691 help.append("\t deleteUserLink <name>\n");
692 help.append("\t printUserLink\n");
693 help.append("\t printNodeEdges\n");
694 return help.toString();
697 public void _printUserLink(CommandInterpreter ci) {
698 for (String name : this.userLinks.keySet()) {
699 TopologyUserLinkConfig linkConfig = userLinks.get(name);
700 ci.println("Name : " + name);
701 ci.println(linkConfig);
702 ci.println("Edge " + getLinkTuple(linkConfig));
703 ci.println("Reverse Edge " + getReverseLinkTuple(linkConfig));
707 public void _addUserLink(CommandInterpreter ci) {
708 String name = ci.nextArgument();
709 if ((name == null)) {
710 ci.println("Please enter a valid Name");
714 String ncStr1 = ci.nextArgument();
715 if (ncStr1 == null) {
716 ci.println("Please enter two node connector strings");
719 String ncStr2 = ci.nextArgument();
720 if (ncStr2 == null) {
721 ci.println("Please enter second node connector string");
725 NodeConnector nc1 = NodeConnector.fromString(ncStr1);
727 ci.println("Invalid input node connector 1 string: " + ncStr1);
730 NodeConnector nc2 = NodeConnector.fromString(ncStr2);
732 ci.println("Invalid input node connector 2 string: " + ncStr2);
736 TopologyUserLinkConfig config = new TopologyUserLinkConfig(name, ncStr1, ncStr2);
737 ci.println(this.addUserLink(config));
740 public void _deleteUserLink(CommandInterpreter ci) {
741 String name = ci.nextArgument();
742 if ((name == null)) {
743 ci.println("Please enter a valid Name");
746 this.deleteUserLink(name);
749 public void _printNodeEdges(CommandInterpreter ci) {
750 Map<Node, Set<Edge>> nodeEdges = getNodeEdges();
751 if (nodeEdges == null) {
754 Set<Node> nodeSet = nodeEdges.keySet();
755 if (nodeSet == null) {
758 ci.println(" Node Edge");
759 for (Node node : nodeSet) {
760 Set<Edge> edgeSet = nodeEdges.get(node);
761 if (edgeSet == null) {
764 for (Edge edge : edgeSet) {
765 ci.println(node + " " + edge);
771 public Object readObject(ObjectInputStream ois)
772 throws FileNotFoundException, IOException, ClassNotFoundException {
773 // TODO Auto-generated method stub
774 return ois.readObject();
778 public Status saveConfiguration() {
783 public void edgeOverUtilized(Edge edge) {
784 log.warn("Link Utilization above normal: {}", edge);
788 public void edgeUtilBackToNormal(Edge edge) {
789 log.warn("Link Utilization back to normal: {}", edge);