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 log.debug("Adding ITopologyManagerAware: " + s);
98 this.topologyManagerAware.add(s);
102 void unsetTopologyManagerAware(ITopologyManagerAware s) {
103 if (this.topologyManagerAware != null) {
104 this.topologyManagerAware.remove(s);
108 void setTopoService(ITopologyService s) {
109 this.topoService = s;
112 void unsetTopoService(ITopologyService s) {
113 if (this.topoService == s) {
114 this.topoService = null;
118 void setClusterContainerService(IClusterContainerServices s) {
119 log.debug("Cluster Service set");
120 this.clusterContainerService = s;
123 void unsetClusterContainerService(IClusterContainerServices s) {
124 if (this.clusterContainerService == s) {
125 log.debug("Cluster Service removed!");
126 this.clusterContainerService = null;
131 * Function called by the dependency manager when all the required
132 * dependencies are satisfied
135 void init(Component c) {
136 String containerName = null;
137 Dictionary props = c.getServiceProperties();
139 containerName = (String) props.get("containerName");
141 // In the Global instance case the containerName is empty
142 containerName = "UNKNOWN";
145 if (this.clusterContainerService == null) {
146 log.error("Cluster Services is null, not expected!");
150 if (this.topoService == null) {
151 log.error("Topology Services is null, not expected!");
156 this.edgesDB = (ConcurrentMap<Edge, Set<Property>>) this.clusterContainerService
157 .createCache("topologymanager.edgesDB", EnumSet
158 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
159 } catch (CacheExistException cee) {
160 log.error("topologymanager.edgesDB Cache already exists - "
161 + "destroy and recreate if needed");
162 } catch (CacheConfigException cce) {
163 log.error("topologymanager.edgesDB Cache configuration invalid - "
164 + "check cache mode");
168 this.hostsDB = (ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>>) this.clusterContainerService
169 .createCache("topologymanager.hostsDB", EnumSet
170 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
171 } catch (CacheExistException cee) {
172 log.error("topologymanager.hostsDB Cache already exists - "
173 + "destroy and recreate if needed");
174 } catch (CacheConfigException cce) {
175 log.error("topologymanager.hostsDB Cache configuration invalid - "
176 + "check cache mode");
180 this.nodeConnectorsDB = (ConcurrentMap<NodeConnector, Set<Property>>) this.clusterContainerService
181 .createCache("topologymanager.nodeConnectorDB", EnumSet
182 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
183 } catch (CacheExistException cee) {
184 log.error("topologymanager.nodeConnectorDB Cache already exists"
185 + " - destroy and recreate if needed");
186 } catch (CacheConfigException cce) {
187 log.error("topologymanager.nodeConnectorDB Cache configuration "
188 + "invalid - check cache mode");
191 userLinks = new ConcurrentHashMap<String, TopologyUserLinkConfig>();
193 userLinksFileName = ROOT + "userTopology_" + containerName + ".conf";
194 registerWithOSGIConsole();
199 * Function called after the topology manager has registered the
200 * service in OSGi service registry.
204 // SollicitRefresh MUST be called here else if called at init
205 // time it may sollicit refresh too soon.
206 log.debug("Sollicit topology refresh");
207 topoService.sollicitRefresh();
211 * Function called by the dependency manager when at least one
212 * dependency become unsatisfied or when the component is shutting
213 * down because for example bundle is being stopped.
217 if (this.clusterContainerService == null) {
218 log.error("Cluster Services is null, not expected!");
221 this.nodeConnectorsDB = null;
224 this.clusterContainerService.destroyCache("topologymanager.edgesDB");
226 this.clusterContainerService.destroyCache("topologymanager.hostsDB");
228 this.clusterContainerService
229 .destroyCache("topologymanager.nodeConnectorDB");
230 this.nodeConnectorsDB = null;
231 log.debug("Topology Manager DB DE-allocated");
234 @SuppressWarnings("unchecked")
235 private void loadConfiguration() {
236 ObjectReader objReader = new ObjectReader();
237 ConcurrentMap<String, TopologyUserLinkConfig> confList = (ConcurrentMap<String, TopologyUserLinkConfig>) objReader
238 .read(this, userLinksFileName);
240 if (confList == null) {
244 for (TopologyUserLinkConfig conf : confList.values()) {
250 public Status saveConfig() {
251 // Publish the save config event to the cluster nodes
253 * Get the CLUSTERING SERVICES WORKING BEFORE TRYING THIS
255 configSaveEvent.put(new Date().getTime(), SAVE);
257 return saveConfigInternal();
260 public Status saveConfigInternal() {
262 ObjectWriter objWriter = new ObjectWriter();
264 retS = objWriter.write(
265 new ConcurrentHashMap<String, TopologyUserLinkConfig>(
266 userLinks), userLinksFileName);
268 if (retS.isSuccess()) {
271 return new Status(StatusCode.INTERNALERROR, "Save failed");
276 public Map<Node, Set<Edge>> getNodeEdges() {
277 if (this.edgesDB == null) {
281 HashMap<Node, Set<Edge>> res = new HashMap<Node, Set<Edge>>();
282 for (Edge key : this.edgesDB.keySet()) {
283 // Lets analyze the tail
284 Node node = key.getTailNodeConnector().getNode();
285 Set<Edge> nodeEdges = res.get(node);
286 if (nodeEdges == null) {
287 nodeEdges = new HashSet<Edge>();
290 // We need to re-add to the MAP even if the element was
291 // already there so in case of clustered services the map
292 // gets updated in the cluster
293 res.put(node, nodeEdges);
295 // Lets analyze the head
296 node = key.getHeadNodeConnector().getNode();
297 nodeEdges = res.get(node);
298 if (nodeEdges == null) {
299 nodeEdges = new HashSet<Edge>();
302 // We need to re-add to the MAP even if the element was
303 // already there so in case of clustered services the map
304 // gets updated in the cluster
305 res.put(node, nodeEdges);
312 public boolean isInternal(NodeConnector p) {
313 if (this.nodeConnectorsDB == null) {
317 // This is an internal NodeConnector if is connected to
318 // another Node i.e it's part of the nodeConnectorsDB
319 return (this.nodeConnectorsDB.get(p) != null);
323 * The Map returned is a copy of the current topology hence if the
324 * topology changes the copy doesn't
326 * @return A Map representing the current topology expressed as
327 * edges of the network
330 public Map<Edge, Set<Property>> getEdges() {
331 if (this.edgesDB == null) {
335 HashMap<Edge, Set<Property>> res = new HashMap<Edge, Set<Property>>();
336 for (Edge key : this.edgesDB.keySet()) {
337 // Sets of props are copied because the composition of
338 // those properties could change with time
339 HashSet<Property> prop = new HashSet<Property>(this.edgesDB
341 // We can simply reuse the key because the object is
342 // immutable so doesn't really matter that we are
343 // referencing the only owned by a different table, the
344 // meaning is the same because doesn't change with time.
351 // TODO remove with spring-dm removal
353 * @param set the topologyAware to set
355 public void setTopologyAware(Set<Object> set) {
356 for (Object s : set) {
357 setTopologyManagerAware((ITopologyManagerAware) s);
362 public Set<NodeConnector> getNodeConnectorWithHost() {
363 if (this.hostsDB == null) {
367 return (this.hostsDB.keySet());
371 public Map<Node, Set<NodeConnector>> getNodesWithNodeConnectorHost() {
372 if (this.hostsDB == null) {
375 HashMap<Node, Set<NodeConnector>> res = new HashMap<Node, Set<NodeConnector>>();
377 for (NodeConnector p : this.hostsDB.keySet()) {
378 Node n = p.getNode();
379 Set<NodeConnector> pSet = res.get(n);
381 // Create the HashSet if null
382 pSet = new HashSet<NodeConnector>();
386 // Keep updating the HashSet, given this is not a
387 // clustered map we can just update the set without
388 // worrying to update the hashmap.
396 public Host getHostAttachedToNodeConnector(NodeConnector p) {
397 if (this.hostsDB == null) {
401 return (this.hostsDB.get(p).getLeft());
405 public void updateHostLink(NodeConnector p, Host h, UpdateType t,
406 Set<Property> props) {
407 if (this.hostsDB == null) {
414 // Clone the property set in case non null else just
415 // create an empty one. Caches allocated via infinispan
416 // don't allow null values
418 props = new HashSet<Property>();
420 props = new HashSet<Property>(props);
423 this.hostsDB.put(p, new ImmutablePair(h, props));
426 this.hostsDB.remove(p);
432 public void edgeUpdate(Edge e, UpdateType type, Set<Property> props) {
435 // Make sure the props are non-null
437 props = (Set<Property>) new HashSet();
439 // Copy the set so noone is going to change the content
440 props = (Set<Property>) new HashSet(props);
443 // Now make sure thre is the creation timestamp for the
444 // edge, if not there timestamp with the first update
445 boolean found_create = false;
446 for (Property prop : props) {
447 if (prop instanceof TimeStamp) {
448 TimeStamp t = (TimeStamp) prop;
449 if (t.getTimeStampName().equals("creation")) {
456 TimeStamp t = new TimeStamp(System.currentTimeMillis(),
461 // Now add this in the database eventually overriding
462 // something that may have been already existing
463 this.edgesDB.put(e, props);
465 // Now populate the DB of NodeConnectors
466 // NOTE WELL: properties are empy sets, not really needed
468 this.nodeConnectorsDB.put(e.getHeadNodeConnector(),
469 new HashSet<Property>());
470 this.nodeConnectorsDB.put(e.getTailNodeConnector(),
471 new HashSet<Property>());
474 // Now remove the edge from edgesDB
475 this.edgesDB.remove(e);
477 // Now lets update the NodeConnectors DB, the assumption
478 // here is that two NodeConnector are exclusively
479 // connected by 1 and only 1 edge, this is reasonable in
480 // the same plug (virtual of phisical) we can assume two
481 // cables won't be plugged. This could break only in case
482 // of devices in the middle that acts as hubs, but it
483 // should be safe to assume that won't happen.
484 this.nodeConnectorsDB.remove(e.getHeadNodeConnector());
485 this.nodeConnectorsDB.remove(e.getTailNodeConnector());
488 Set<Property> old_props = this.edgesDB.get(e);
490 // When property changes lets make sure we can change it
491 // all except the creation time stamp because that should
492 // be changed only when the edge is destroyed and created
495 for (Property prop : old_props) {
496 if (prop instanceof TimeStamp) {
497 TimeStamp t = (TimeStamp) prop;
498 if (t.getTimeStampName().equals("creation")) {
504 // Now lest make sure new properties are non-null
505 // Make sure the props are non-null
507 props = (Set<Property>) new HashSet();
509 // Copy the set so noone is going to change the content
510 props = (Set<Property>) new HashSet(props);
513 // Now lets remove the creation property if exist in the
515 for (Iterator<Property> i = props.iterator(); i.hasNext();) {
516 Property prop = i.next();
517 if (prop instanceof TimeStamp) {
518 TimeStamp t = (TimeStamp) prop;
519 if (t.getTimeStampName().equals("creation")) {
525 // Now lets add the creation timestamp in it
531 this.edgesDB.put(e, props);
535 // Now update the listeners
536 for (ITopologyManagerAware s : this.topologyManagerAware) {
538 s.edgeUpdate(e, type, props);
539 } catch (Exception exc) {
540 log.error("Exception on callback", exc);
545 private Edge getReverseLinkTuple(TopologyUserLinkConfig link) {
546 TopologyUserLinkConfig rLink = new TopologyUserLinkConfig(link
547 .getName(), link.getDstSwitchId(), link.getDstPort(), link
548 .getSrcSwitchId(), link.getSrcPort());
549 return getLinkTuple(rLink);
552 private Edge getLinkTuple(TopologyUserLinkConfig link) {
553 Edge linkTuple = null;
554 Long sID = link.getSrcSwitchIDLong();
555 Long dID = link.getDstSwitchIDLong();
556 Short srcPort = Short.valueOf((short) 0);
557 Short dstPort = Short.valueOf((short) 0);
558 if (link.isSrcPortByName()) {
559 // TODO find the inventory service to do this, for now 0
560 //srcPort = srcSw.getPortNumber(link.getSrcPort());
562 srcPort = Short.parseShort(link.getSrcPort());
565 if (link.isDstPortByName()) {
566 //dstPort = dstSw.getPortNumber(link.getDstPort());;
568 dstPort = Short.parseShort(link.getDstPort());
571 // if atleast 1 link exists for the srcPort and atleast 1 link exists for the dstPort
572 // that makes it ineligible for the Manual link addition
573 // This is just an extra protection to avoid mis-programming.
574 boolean srcLinkExists = false;
575 boolean dstLinkExists = false;
577 * Disabling this optimization for now to understand the real benefit of doing this.
578 * Since this is a Manual Link addition, the user knows what he is doing and it is
579 * not good to restrict such creativity...
582 Set <Edge> links = oneTopology.getLinks().keySet();
584 for (Edge eLink : links) {
585 if (!eLink.isUserCreated() &&
586 eLink.getSrc().getSid().equals(link.getSrcSwitchIDLong()) &&
587 eLink.getSrc().getPort().equals(srcPort)) {
588 srcLinkExists = true;
591 if (!eLink.isUserCreated() &&
592 eLink.getSrc().getSid().equals(link.getSrcSwitchIDLong()) &&
593 eLink.getSrc().getPort().equals(srcPort)) {
594 dstLinkExists = true;
597 if (!eLink.isUserCreated() &&
598 eLink.getDst().getSid().equals(link.getSrcSwitchIDLong()) &&
599 eLink.getDst().getPort().equals(srcPort)) {
600 srcLinkExists = true;
603 if (!eLink.isUserCreated() &&
604 eLink.getDst().getSid().equals(link.getSrcSwitchIDLong()) &&
605 eLink.getDst().getPort().equals(srcPort)) {
606 dstLinkExists = true;
611 //TODO check a way to validate the port with inventory services
612 //if (srcSw.getPorts().contains(srcPort) &&
613 //dstSw.getPorts().contains(srcPort) &&
614 if (!srcLinkExists && !dstLinkExists) {
617 NodeConnector sPort = null;
618 NodeConnector dPort = null;
621 sNode = new Node(Node.NodeIDType.OPENFLOW, sID);
622 dNode = new Node(Node.NodeIDType.OPENFLOW, dID);
623 sPort = new NodeConnector(
624 NodeConnector.NodeConnectorIDType.OPENFLOW, srcPort,
626 dPort = new NodeConnector(
627 NodeConnector.NodeConnectorIDType.OPENFLOW, dstPort,
629 linkTuple = new Edge(sPort, dPort);
630 } catch (ConstructionException cex) {
635 if (srcLinkExists && dstLinkExists) {
636 link.setStatus(TopologyUserLinkConfig.STATUS.INCORRECT);
642 public ConcurrentMap<String, TopologyUserLinkConfig> getUserLinks() {
647 public Status addUserLink(TopologyUserLinkConfig link) {
648 if (!link.isValid()) {
649 return new Status(StatusCode.BADREQUEST,
650 "Configuration Invalid. Please check the parameters");
652 if (userLinks.get(link.getName()) != null) {
653 return new Status(StatusCode.CONFLICT,
654 "Link with name : " + link.getName()
655 + " already exists. Please use another name");
657 if (userLinks.containsValue(link)) {
658 return new Status(StatusCode.CONFLICT, "Link configuration exists");
661 link.setStatus(TopologyUserLinkConfig.STATUS.LINKDOWN);
662 userLinks.put(link.getName(), link);
664 Edge linkTuple = getLinkTuple(link);
665 if (linkTuple != null) {
667 // TODO The onetopology will be gone too, topology
668 //manager is the master of the topology at this point
669 //if (oneTopology.addUserConfiguredLink(linkTuple)) {
670 linkTuple = getReverseLinkTuple(link);
671 //if (oneTopology.addUserConfiguredLink(linkTuple)) {
672 link.setStatus(TopologyUserLinkConfig.STATUS.SUCCESS);
675 } catch (Exception e) {
676 return new Status(StatusCode.INTERNALERROR,
677 "Exception while adding custom link : " +
681 return new Status(StatusCode.SUCCESS, null);
685 public Status deleteUserLink(String linkName) {
686 if (linkName == null) {
687 return new Status(StatusCode.BADREQUEST,
688 "A valid linkName is required to Delete a link");
691 TopologyUserLinkConfig link = userLinks.get(linkName);
693 Edge linkTuple = getLinkTuple(link);
694 userLinks.remove(linkName);
695 if (linkTuple != null) {
697 //oneTopology.deleteUserConfiguredLink(linkTuple);
698 } catch (Exception e) {
700 .warn("Harmless : Exception while Deleting User Configured link "
701 + link + " " + e.toString());
703 linkTuple = getReverseLinkTuple(link);
705 //oneTopology.deleteUserConfiguredLink(linkTuple);
706 } catch (Exception e) {
708 .error("Harmless : Exception while Deleting User Configured Reverse link "
709 + link + " " + e.toString());
712 return new Status(StatusCode.SUCCESS, null);
715 private void registerWithOSGIConsole() {
716 BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
718 bundleContext.registerService(CommandProvider.class.getName(), this,
723 public String getHelp() {
724 StringBuffer help = new StringBuffer();
725 help.append("---Topology Manager---\n");
727 .append("\t addTopo name <src-sw-id> <port-number> <dst-sw-id> <port-number>\n");
728 help.append("\t delTopo name\n");
729 help.append("\t _printTopo\n");
730 return help.toString();
733 public void _printTopo(CommandInterpreter ci) {
734 for (String name : this.userLinks.keySet()) {
735 ci.println(name + " : " + userLinks.get(name));
739 public void _addTopo(CommandInterpreter ci) {
740 String name = ci.nextArgument();
741 if ((name == null)) {
742 ci.println("Please enter a valid Name");
746 String dpid = ci.nextArgument();
748 ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
752 HexEncode.stringToLong(dpid);
753 } catch (Exception e) {
754 ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
758 String port = ci.nextArgument();
760 ci.println("Invalid port number");
764 String ddpid = ci.nextArgument();
766 ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
770 HexEncode.stringToLong(ddpid);
771 } catch (Exception e) {
772 ci.println("Invalid Switch ID. Format xx:xx:xx:xx:xx:xx:xx:xx");
776 String dport = ci.nextArgument();
778 ci.println("Invalid port number");
781 TopologyUserLinkConfig config = new TopologyUserLinkConfig(name,
782 dpid, port, ddpid, dport);
783 ci.println(this.addUserLink(config));
786 public void _delTopo(CommandInterpreter ci) {
787 String name = ci.nextArgument();
788 if ((name == null)) {
789 ci.println("Please enter a valid Name");
792 this.deleteUserLink(name);
796 public Object readObject(ObjectInputStream ois)
797 throws FileNotFoundException, IOException, ClassNotFoundException {
798 // TODO Auto-generated method stub
799 return ois.readObject();
803 public Status saveConfiguration() {
808 public void edgeOverUtilized(Edge edge) {
809 log.warn("Link Utilization above normal: " + edge);
813 public void edgeUtilBackToNormal(Edge edge) {
814 log.warn("Link Utilization back to normal: " + edge);