3 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
5 * This program and the accompanying materials are made available under the
6 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7 * and is available at http://www.eclipse.org/legal/epl-v10.html
10 package org.opendaylight.controller.topologymanager.internal;
12 import java.io.FileNotFoundException;
13 import java.io.IOException;
14 import java.io.ObjectInputStream;
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;
23 import java.util.concurrent.ConcurrentHashMap;
24 import java.util.concurrent.ConcurrentMap;
26 import org.apache.commons.lang3.tuple.ImmutablePair;
27 import org.apache.felix.dm.Component;
28 import org.eclipse.osgi.framework.console.CommandInterpreter;
29 import org.eclipse.osgi.framework.console.CommandProvider;
30 import org.opendaylight.controller.clustering.services.CacheConfigException;
31 import org.opendaylight.controller.clustering.services.CacheExistException;
32 import org.opendaylight.controller.clustering.services.IClusterContainerServices;
33 import org.opendaylight.controller.clustering.services.IClusterServices;
34 import org.opendaylight.controller.configuration.IConfigurationContainerAware;
35 import org.opendaylight.controller.sal.core.ConstructionException;
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.utils.StatusCode;
46 import org.opendaylight.controller.sal.utils.GlobalConstants;
47 import org.opendaylight.controller.sal.utils.HexEncode;
48 import org.opendaylight.controller.sal.utils.IObjectReader;
49 import org.opendaylight.controller.sal.utils.ObjectReader;
50 import org.opendaylight.controller.sal.utils.ObjectWriter;
51 import org.opendaylight.controller.sal.utils.Status;
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 Edges, meaning they
75 // are connected to another NodeConnector on the other side
76 private ConcurrentMap<NodeConnector, Set<Property>> nodeConnectorsDB = null;
77 // DB of all the NodeConnectors with an Host attached to it
78 private ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>> hostsDB = null;
79 // Topology Manager Aware listeners
80 private Set<ITopologyManagerAware> topologyManagerAware = Collections
81 .synchronizedSet(new HashSet<ITopologyManagerAware>());
83 private static String ROOT = GlobalConstants.STARTUPHOME.toString();
84 private String userLinksFileName = null;
85 private ConcurrentMap<String, TopologyUserLinkConfig> userLinks;
87 void nonClusterObjectCreate() {
88 edgesDB = new ConcurrentHashMap<Edge, Set<Property>>();
89 hostsDB = new ConcurrentHashMap<NodeConnector, ImmutablePair<Host, Set<Property>>>();
90 userLinks = new ConcurrentHashMap<String, TopologyUserLinkConfig>();
91 nodeConnectorsDB = new ConcurrentHashMap<NodeConnector, Set<Property>>();
95 void setTopologyManagerAware(ITopologyManagerAware s) {
96 if (this.topologyManagerAware != null) {
97 this.topologyManagerAware.add(s);
101 void unsetTopologyManagerAware(ITopologyManagerAware s) {
102 if (this.topologyManagerAware != null) {
103 this.topologyManagerAware.remove(s);
107 void setTopoService(ITopologyService s) {
108 this.topoService = s;
111 void unsetTopoService(ITopologyService s) {
112 if (this.topoService == s) {
113 this.topoService = null;
117 void setClusterContainerService(IClusterContainerServices s) {
118 log.debug("Cluster Service set");
119 this.clusterContainerService = s;
122 void unsetClusterContainerService(IClusterContainerServices s) {
123 if (this.clusterContainerService == s) {
124 log.debug("Cluster Service removed!");
125 this.clusterContainerService = null;
130 * Function called by the dependency manager when all the required
131 * dependencies are satisfied
134 void init(Component c) {
135 String containerName = null;
136 Dictionary props = c.getServiceProperties();
138 containerName = (String) props.get("containerName");
140 // In the Global instance case the containerName is empty
141 containerName = "UNKNOWN";
144 if (this.clusterContainerService == null) {
145 log.error("Cluster Services is null, not expected!");
149 if (this.topoService == null) {
150 log.error("Topology Services is null, not expected!");
155 this.edgesDB = (ConcurrentMap<Edge, Set<Property>>) this.clusterContainerService
156 .createCache("topologymanager.edgesDB", EnumSet
157 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
158 } catch (CacheExistException cee) {
159 log.error("topologymanager.edgesDB Cache already exists - "
160 + "destroy and recreate if needed");
161 } catch (CacheConfigException cce) {
162 log.error("topologymanager.edgesDB Cache configuration invalid - "
163 + "check cache mode");
167 this.hostsDB = (ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>>) this.clusterContainerService
168 .createCache("topologymanager.hostsDB", EnumSet
169 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
170 } catch (CacheExistException cee) {
171 log.error("topologymanager.hostsDB Cache already exists - "
172 + "destroy and recreate if needed");
173 } catch (CacheConfigException cce) {
174 log.error("topologymanager.hostsDB Cache configuration invalid - "
175 + "check cache mode");
179 this.nodeConnectorsDB = (ConcurrentMap<NodeConnector, Set<Property>>) this.clusterContainerService
180 .createCache("topologymanager.nodeConnectorDB", EnumSet
181 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
182 } catch (CacheExistException cee) {
183 log.error("topologymanager.nodeConnectorDB Cache already exists"
184 + " - destroy and recreate if needed");
185 } catch (CacheConfigException cce) {
186 log.error("topologymanager.nodeConnectorDB Cache configuration "
187 + "invalid - check cache mode");
190 userLinks = new ConcurrentHashMap<String, TopologyUserLinkConfig>();
192 userLinksFileName = ROOT + "userTopology_" + containerName + ".conf";
193 registerWithOSGIConsole();
198 * Function called after the topology manager has registered the
199 * service in OSGi service registry.
203 // SollicitRefresh MUST be called here else if called at init
204 // time it may sollicit refresh too soon.
205 log.debug("Sollicit topology refresh");
206 topoService.sollicitRefresh();
210 * Function called by the dependency manager when at least one
211 * dependency become unsatisfied or when the component is shutting
212 * down because for example bundle is being stopped.
216 if (this.clusterContainerService == null) {
217 log.error("Cluster Services is null, not expected!");
220 this.nodeConnectorsDB = null;
223 this.clusterContainerService.destroyCache("topologymanager.edgesDB");
225 this.clusterContainerService.destroyCache("topologymanager.hostsDB");
227 this.clusterContainerService
228 .destroyCache("topologymanager.nodeConnectorDB");
229 this.nodeConnectorsDB = null;
230 log.debug("Topology Manager DB DE-allocated");
233 @SuppressWarnings("unchecked")
234 private void loadConfiguration() {
235 ObjectReader objReader = new ObjectReader();
236 ConcurrentMap<String, TopologyUserLinkConfig> confList = (ConcurrentMap<String, TopologyUserLinkConfig>) objReader
237 .read(this, userLinksFileName);
239 if (confList == null) {
243 for (TopologyUserLinkConfig conf : confList.values()) {
249 public Status saveConfig() {
250 // Publish the save config event to the cluster nodes
252 * Get the CLUSTERING SERVICES WORKING BEFORE TRYING THIS
254 configSaveEvent.put(new Date().getTime(), SAVE);
256 return saveConfigInternal();
259 public Status saveConfigInternal() {
261 ObjectWriter objWriter = new ObjectWriter();
263 retS = objWriter.write(
264 new ConcurrentHashMap<String, TopologyUserLinkConfig>(
265 userLinks), userLinksFileName);
267 if (retS.isSuccess()) {
270 return new Status(StatusCode.INTERNALERROR, "Save failed");
275 public Map<Node, Set<Edge>> getNodeEdges() {
276 if (this.edgesDB == null) {
280 HashMap<Node, Set<Edge>> res = new HashMap<Node, Set<Edge>>();
281 for (Edge key : this.edgesDB.keySet()) {
282 // Lets analyze the tail
283 Node node = key.getTailNodeConnector().getNode();
284 Set<Edge> nodeEdges = res.get(node);
285 if (nodeEdges == null) {
286 nodeEdges = new HashSet<Edge>();
289 // We need to re-add to the MAP even if the element was
290 // already there so in case of clustered services the map
291 // gets updated in the cluster
292 res.put(node, nodeEdges);
294 // Lets analyze the head
295 node = key.getHeadNodeConnector().getNode();
296 nodeEdges = res.get(node);
297 if (nodeEdges == null) {
298 nodeEdges = new HashSet<Edge>();
301 // We need to re-add to the MAP even if the element was
302 // already there so in case of clustered services the map
303 // gets updated in the cluster
304 res.put(node, nodeEdges);
311 public boolean isInternal(NodeConnector p) {
312 if (this.nodeConnectorsDB == null) {
316 // This is an internal NodeConnector if is connected to
317 // another Node i.e it's part of the nodeConnectorsDB
318 return (this.nodeConnectorsDB.get(p) != null);
322 * The Map returned is a copy of the current topology hence if the
323 * topology changes the copy doesn't
325 * @return A Map representing the current topology expressed as
326 * edges of the network
329 public Map<Edge, Set<Property>> getEdges() {
330 if (this.edgesDB == null) {
334 HashMap<Edge, Set<Property>> res = new HashMap<Edge, Set<Property>>();
335 for (Edge key : this.edgesDB.keySet()) {
336 // Sets of props are copied because the composition of
337 // those properties could change with time
338 HashSet<Property> prop = new HashSet<Property>(this.edgesDB
340 // We can simply reuse the key because the object is
341 // immutable so doesn't really matter that we are
342 // referencing the only owned by a different table, the
343 // meaning is the same because doesn't change with time.
350 // TODO remove with spring-dm removal
352 * @param set the topologyAware to set
354 public void setTopologyAware(Set<Object> set) {
355 for (Object s : set) {
356 setTopologyManagerAware((ITopologyManagerAware) s);
361 public Set<NodeConnector> getNodeConnectorWithHost() {
362 if (this.hostsDB == null) {
366 return (this.hostsDB.keySet());
370 public Map<Node, Set<NodeConnector>> getNodesWithNodeConnectorHost() {
371 if (this.hostsDB == null) {
374 HashMap<Node, Set<NodeConnector>> res = new HashMap<Node, Set<NodeConnector>>();
376 for (NodeConnector p : this.hostsDB.keySet()) {
377 Node n = p.getNode();
378 Set<NodeConnector> pSet = res.get(n);
380 // Create the HashSet if null
381 pSet = new HashSet<NodeConnector>();
385 // Keep updating the HashSet, given this is not a
386 // clustered map we can just update the set without
387 // worrying to update the hashmap.
395 public Host getHostAttachedToNodeConnector(NodeConnector p) {
396 if (this.hostsDB == null) {
400 return (this.hostsDB.get(p).getLeft());
404 public void updateHostLink(NodeConnector p, Host h, UpdateType t,
405 Set<Property> props) {
406 if (this.hostsDB == null) {
413 // Clone the property set in case non null else just
414 // create an empty one. Caches allocated via infinispan
415 // don't allow null values
417 props = new HashSet<Property>();
419 props = new HashSet<Property>(props);
422 this.hostsDB.put(p, new ImmutablePair(h, props));
425 this.hostsDB.remove(p);
431 public void edgeUpdate(Edge e, UpdateType type, Set<Property> props) {
434 // Make sure the props are non-null
436 props = (Set<Property>) new HashSet();
438 // Copy the set so noone is going to change the content
439 props = (Set<Property>) new HashSet(props);
442 // Now make sure thre is the creation timestamp for the
443 // edge, if not there timestamp with the first update
444 boolean found_create = false;
445 for (Property prop : props) {
446 if (prop instanceof TimeStamp) {
447 TimeStamp t = (TimeStamp) prop;
448 if (t.getTimeStampName().equals("creation")) {
455 TimeStamp t = new TimeStamp(System.currentTimeMillis(),
460 // Now add this in the database eventually overriding
461 // something that may have been already existing
462 this.edgesDB.put(e, props);
464 // Now populate the DB of NodeConnectors
465 // NOTE WELL: properties are empy sets, not really needed
467 this.nodeConnectorsDB.put(e.getHeadNodeConnector(),
468 new HashSet<Property>());
469 this.nodeConnectorsDB.put(e.getTailNodeConnector(),
470 new HashSet<Property>());
473 // Now remove the edge from edgesDB
474 this.edgesDB.remove(e);
476 // Now lets update the NodeConnectors DB, the assumption
477 // here is that two NodeConnector are exclusively
478 // connected by 1 and only 1 edge, this is reasonable in
479 // the same plug (virtual of phisical) we can assume two
480 // cables won't be plugged. This could break only in case
481 // of devices in the middle that acts as hubs, but it
482 // should be safe to assume that won't happen.
483 this.nodeConnectorsDB.remove(e.getHeadNodeConnector());
484 this.nodeConnectorsDB.remove(e.getTailNodeConnector());
487 Set<Property> old_props = this.edgesDB.get(e);
489 // When property changes lets make sure we can change it
490 // all except the creation time stamp because that should
491 // be changed only when the edge is destroyed and created
494 for (Property prop : old_props) {
495 if (prop instanceof TimeStamp) {
496 TimeStamp t = (TimeStamp) prop;
497 if (t.getTimeStampName().equals("creation")) {
503 // Now lest make sure new properties are non-null
504 // Make sure the props are non-null
506 props = (Set<Property>) new HashSet();
508 // Copy the set so noone is going to change the content
509 props = (Set<Property>) new HashSet(props);
512 // Now lets remove the creation property if exist in the
514 for (Iterator<Property> i = props.iterator(); i.hasNext();) {
515 Property prop = i.next();
516 if (prop instanceof TimeStamp) {
517 TimeStamp t = (TimeStamp) prop;
518 if (t.getTimeStampName().equals("creation")) {
524 // Now lets add the creation timestamp in it
530 this.edgesDB.put(e, props);
534 // Now update the listeners
535 for (ITopologyManagerAware s : this.topologyManagerAware) {
537 s.edgeUpdate(e, type, props);
538 } catch (Exception exc) {
539 log.error("Exception on callback", exc);
544 private Edge getReverseLinkTuple(TopologyUserLinkConfig link) {
545 TopologyUserLinkConfig rLink = new TopologyUserLinkConfig(link
546 .getName(), link.getDstSwitchId(), link.getDstPort(), link
547 .getSrcSwitchId(), link.getSrcPort());
548 return getLinkTuple(rLink);
551 private Edge getLinkTuple(TopologyUserLinkConfig link) {
552 Edge linkTuple = null;
553 Long sID = link.getSrcSwitchIDLong();
554 Long dID = link.getDstSwitchIDLong();
555 Short srcPort = Short.valueOf((short) 0);
556 Short dstPort = Short.valueOf((short) 0);
557 if (link.isSrcPortByName()) {
558 // TODO find the inventory service to do this, for now 0
559 //srcPort = srcSw.getPortNumber(link.getSrcPort());
561 srcPort = Short.parseShort(link.getSrcPort());
564 if (link.isDstPortByName()) {
565 //dstPort = dstSw.getPortNumber(link.getDstPort());;
567 dstPort = Short.parseShort(link.getDstPort());
570 // if atleast 1 link exists for the srcPort and atleast 1 link exists for the dstPort
571 // that makes it ineligible for the Manual link addition
572 // This is just an extra protection to avoid mis-programming.
573 boolean srcLinkExists = false;
574 boolean dstLinkExists = false;
576 * Disabling this optimization for now to understand the real benefit of doing this.
577 * Since this is a Manual Link addition, the user knows what he is doing and it is
578 * not good to restrict such creativity...
581 Set <Edge> links = oneTopology.getLinks().keySet();
583 for (Edge eLink : links) {
584 if (!eLink.isUserCreated() &&
585 eLink.getSrc().getSid().equals(link.getSrcSwitchIDLong()) &&
586 eLink.getSrc().getPort().equals(srcPort)) {
587 srcLinkExists = true;
590 if (!eLink.isUserCreated() &&
591 eLink.getSrc().getSid().equals(link.getSrcSwitchIDLong()) &&
592 eLink.getSrc().getPort().equals(srcPort)) {
593 dstLinkExists = true;
596 if (!eLink.isUserCreated() &&
597 eLink.getDst().getSid().equals(link.getSrcSwitchIDLong()) &&
598 eLink.getDst().getPort().equals(srcPort)) {
599 srcLinkExists = true;
602 if (!eLink.isUserCreated() &&
603 eLink.getDst().getSid().equals(link.getSrcSwitchIDLong()) &&
604 eLink.getDst().getPort().equals(srcPort)) {
605 dstLinkExists = true;
610 //TODO check a way to validate the port with inventory services
611 //if (srcSw.getPorts().contains(srcPort) &&
612 //dstSw.getPorts().contains(srcPort) &&
613 if (!srcLinkExists && !dstLinkExists) {
616 NodeConnector sPort = null;
617 NodeConnector dPort = null;
620 sNode = new Node(Node.NodeIDType.OPENFLOW, sID);
621 dNode = new Node(Node.NodeIDType.OPENFLOW, dID);
622 sPort = new NodeConnector(
623 NodeConnector.NodeConnectorIDType.OPENFLOW, srcPort,
625 dPort = new NodeConnector(
626 NodeConnector.NodeConnectorIDType.OPENFLOW, dstPort,
628 linkTuple = new Edge(sPort, dPort);
629 } catch (ConstructionException cex) {
634 if (srcLinkExists && dstLinkExists) {
635 link.setStatus(TopologyUserLinkConfig.STATUS.INCORRECT);
641 public ConcurrentMap<String, TopologyUserLinkConfig> getUserLinks() {
646 public Status addUserLink(TopologyUserLinkConfig link) {
647 if (!link.isValid()) {
648 return new Status(StatusCode.BADREQUEST,
649 "Configuration Invalid. Please check the parameters");
651 if (userLinks.get(link.getName()) != null) {
652 return new Status(StatusCode.CONFLICT,
653 "Link with name : " + link.getName()
654 + " already exists. Please use another name");
656 if (userLinks.containsValue(link)) {
657 return new Status(StatusCode.CONFLICT, "Link configuration exists");
660 link.setStatus(TopologyUserLinkConfig.STATUS.LINKDOWN);
661 userLinks.put(link.getName(), link);
663 Edge linkTuple = getLinkTuple(link);
664 if (linkTuple != null) {
666 // TODO The onetopology will be gone too, topology
667 //manager is the master of the topology at this point
668 //if (oneTopology.addUserConfiguredLink(linkTuple)) {
669 linkTuple = getReverseLinkTuple(link);
670 //if (oneTopology.addUserConfiguredLink(linkTuple)) {
671 link.setStatus(TopologyUserLinkConfig.STATUS.SUCCESS);
674 } catch (Exception e) {
675 return new Status(StatusCode.INTERNALERROR,
676 "Exception while adding custom link : " +
680 return new Status(StatusCode.SUCCESS, null);
684 public Status deleteUserLink(String linkName) {
685 if (linkName == null) {
686 return new Status(StatusCode.BADREQUEST,
687 "A valid linkName is required to Delete a link");
690 TopologyUserLinkConfig link = userLinks.get(linkName);
692 Edge linkTuple = getLinkTuple(link);
693 userLinks.remove(linkName);
694 if (linkTuple != null) {
696 //oneTopology.deleteUserConfiguredLink(linkTuple);
697 } catch (Exception e) {
699 .warn("Harmless : Exception while Deleting User Configured link "
700 + link + " " + e.toString());
702 linkTuple = getReverseLinkTuple(link);
704 //oneTopology.deleteUserConfiguredLink(linkTuple);
705 } catch (Exception e) {
707 .error("Harmless : Exception while Deleting User Configured Reverse link "
708 + link + " " + e.toString());
711 return new Status(StatusCode.SUCCESS, null);
714 private void registerWithOSGIConsole() {
715 BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
717 bundleContext.registerService(CommandProvider.class.getName(), this,
722 public String getHelp() {
723 StringBuffer help = new StringBuffer();
724 help.append("---Topology Manager---\n");
726 .append("\t addTopo name <src-sw-id> <port-number> <dst-sw-id> <port-number>\n");
727 help.append("\t delTopo name\n");
728 help.append("\t _printTopo\n");
729 return help.toString();
732 public void _printTopo(CommandInterpreter ci) {
733 for (String name : this.userLinks.keySet()) {
734 ci.println(name + " : " + userLinks.get(name));
738 public void _addTopo(CommandInterpreter ci) {
739 String name = ci.nextArgument();
740 if ((name == null)) {
741 ci.println("Please enter a valid Name");
745 String dpid = ci.nextArgument();
747 ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
751 HexEncode.stringToLong(dpid);
752 } catch (Exception e) {
753 ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
757 String port = ci.nextArgument();
759 ci.println("Invalid port number");
763 String ddpid = ci.nextArgument();
765 ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
769 HexEncode.stringToLong(ddpid);
770 } catch (Exception e) {
771 ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
775 String dport = ci.nextArgument();
777 ci.println("Invalid port number");
780 TopologyUserLinkConfig config = new TopologyUserLinkConfig(name,
781 dpid, port, ddpid, dport);
782 ci.println(this.addUserLink(config));
785 public void _delTopo(CommandInterpreter ci) {
786 String name = ci.nextArgument();
787 if ((name == null)) {
788 ci.println("Please enter a valid Name");
791 this.deleteUserLink(name);
795 public Object readObject(ObjectInputStream ois)
796 throws FileNotFoundException, IOException, ClassNotFoundException {
797 // TODO Auto-generated method stub
798 return ois.readObject();
802 public Status saveConfiguration() {
807 public void edgeOverUtilized(Edge edge) {
808 log.warn("Link Utilization above normal: " + edge);
812 public void edgeUtilBackToNormal(Edge edge) {
813 log.warn("Link Utilization back to normal: " + edge);