ce39fed5fcc4960efff1d8959335ced6be066893
[controller.git] / opendaylight / topologymanager / src / main / java / org / opendaylight / controller / topologymanager / internal / TopologyManagerImpl.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.controller.topologymanager.internal;
10
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;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.concurrent.ConcurrentHashMap;
25 import java.util.concurrent.ConcurrentMap;
26
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;
59
60 /**
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.
64  */
65 public class TopologyManagerImpl implements ITopologyManager,
66 IConfigurationContainerAware, IListenTopoUpdates, IObjectReader,
67 CommandProvider {
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>());
83
84     private static String ROOT = GlobalConstants.STARTUPHOME.toString();
85     private String userLinksFileName = null;
86     private ConcurrentMap<String, TopologyUserLinkConfig> userLinks;
87
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>>();
93     }
94
95     void setTopologyManagerAware(ITopologyManagerAware s) {
96         if (this.topologyManagerAware != null) {
97             log.debug("Adding ITopologyManagerAware: {}", s);
98             this.topologyManagerAware.add(s);
99         }
100     }
101
102     void unsetTopologyManagerAware(ITopologyManagerAware s) {
103         if (this.topologyManagerAware != null) {
104             log.debug("Removing ITopologyManagerAware: {}", s);
105             this.topologyManagerAware.remove(s);
106         }
107     }
108
109     void setTopoService(ITopologyService s) {
110         log.debug("Adding ITopologyService: {}", s);
111         this.topoService = s;
112     }
113
114     void unsetTopoService(ITopologyService s) {
115         if (this.topoService == s) {
116             log.debug("Removing ITopologyService: {}", s);
117             this.topoService = null;
118         }
119     }
120
121     void setClusterContainerService(IClusterContainerServices s) {
122         log.debug("Cluster Service set");
123         this.clusterContainerService = s;
124     }
125
126     void unsetClusterContainerService(IClusterContainerServices s) {
127         if (this.clusterContainerService == s) {
128             log.debug("Cluster Service removed!");
129             this.clusterContainerService = null;
130         }
131     }
132
133     /**
134      * Function called by the dependency manager when all the required
135      * dependencies are satisfied
136      *
137      */
138     void init(Component c) {
139         String containerName = null;
140         Dictionary props = c.getServiceProperties();
141         if (props != null) {
142             containerName = (String) props.get("containerName");
143         } else {
144             // In the Global instance case the containerName is empty
145             containerName = "UNKNOWN";
146         }
147
148         if (this.clusterContainerService == null) {
149             log.error("Cluster Services is null, not expected!");
150             return;
151         }
152
153         if (this.topoService == null) {
154             log.error("Topology Services is null, not expected!");
155             return;
156         }
157
158         try {
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");
168         }
169
170         try {
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");
180         }
181
182         try {
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");
192         }
193
194         userLinks = new ConcurrentHashMap<String, TopologyUserLinkConfig>();
195
196         userLinksFileName = ROOT + "userTopology_" + containerName + ".conf";
197         registerWithOSGIConsole();
198         loadConfiguration();
199     }
200
201     /**
202      * Function called after the topology manager has registered the service in
203      * OSGi service registry.
204      *
205      */
206     void started() {
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();
211     }
212
213     /**
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.
217      *
218      */
219     void destroy() {
220         if (this.clusterContainerService == null) {
221             log.error("Cluster Services is null, not expected!");
222             this.edgesDB = null;
223             this.hostsDB = null;
224             this.nodeConnectorsDB = null;
225             return;
226         }
227         this.clusterContainerService.destroyCache("topologymanager.edgesDB");
228         this.edgesDB = null;
229         this.clusterContainerService.destroyCache("topologymanager.hostsDB");
230         this.hostsDB = null;
231         this.clusterContainerService
232         .destroyCache("topologymanager.nodeConnectorDB");
233         this.nodeConnectorsDB = null;
234         log.debug("Topology Manager DB Deallocated");
235     }
236
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);
242
243         if (confList == null) {
244             return;
245         }
246
247         for (TopologyUserLinkConfig conf : confList.values()) {
248             addUserLink(conf);
249         }
250     }
251
252     @Override
253     public Status saveConfig() {
254         // Publish the save config event to the cluster nodes
255         /**
256          * Get the CLUSTERING SERVICES WORKING BEFORE TRYING THIS
257          *
258          * configSaveEvent.put(new Date().getTime(), SAVE);
259          */
260         return saveConfigInternal();
261     }
262
263     public Status saveConfigInternal() {
264         Status retS;
265         ObjectWriter objWriter = new ObjectWriter();
266
267         retS = objWriter
268                 .write(new ConcurrentHashMap<String, TopologyUserLinkConfig>(
269                         userLinks), userLinksFileName);
270
271         if (retS.isSuccess()) {
272             return retS;
273         } else {
274             return new Status(StatusCode.INTERNALERROR, "Save failed");
275         }
276     }
277
278     @Override
279     public Map<Node, Set<Edge>> getNodeEdges() {
280         if (this.edgesDB == null) {
281             return null;
282         }
283
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>();
291             }
292             nodeEdges.add(key);
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);
297
298             // Lets analyze the head
299             node = key.getHeadNodeConnector().getNode();
300             nodeEdges = res.get(node);
301             if (nodeEdges == null) {
302                 nodeEdges = new HashSet<Edge>();
303             }
304             nodeEdges.add(key);
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);
309         }
310
311         return res;
312     }
313
314     @Override
315     public boolean isInternal(NodeConnector p) {
316         if (this.nodeConnectorsDB == null) {
317             return false;
318         }
319
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);
323     }
324
325     /**
326      * This method returns true if the edge is an ISL link.
327      *
328      * @param e
329      *            The edge
330      * @return true if it is an ISL link
331      */
332     public boolean isISLink(Edge e) {
333         return (!isProductionLink(e));
334     }
335
336     /**
337      * This method returns true if the edge is a production link.
338      *
339      * @param e
340      *            The edge
341      * @return true if it is a production link
342      */
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));
348     }
349
350     /**
351      * The Map returned is a copy of the current topology hence if the topology
352      * changes the copy doesn't
353      *
354      * @return A Map representing the current topology expressed as edges of the
355      *         network
356      */
357     @Override
358     public Map<Edge, Set<Property>> getEdges() {
359         if (this.edgesDB == null) {
360             return null;
361         }
362
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.
373             res.put(key, prop);
374         }
375
376         return res;
377     }
378
379     // TODO remove with spring-dm removal
380     /**
381      * @param set
382      *            the topologyAware to set
383      */
384     public void setTopologyAware(Set<Object> set) {
385         for (Object s : set) {
386             setTopologyManagerAware((ITopologyManagerAware) s);
387         }
388     }
389
390     @Override
391     public Set<NodeConnector> getNodeConnectorWithHost() {
392         if (this.hostsDB == null) {
393             return null;
394         }
395
396         return (this.hostsDB.keySet());
397     }
398
399     @Override
400     public Map<Node, Set<NodeConnector>> getNodesWithNodeConnectorHost() {
401         if (this.hostsDB == null) {
402             return null;
403         }
404         HashMap<Node, Set<NodeConnector>> res = new HashMap<Node, Set<NodeConnector>>();
405
406         for (NodeConnector p : this.hostsDB.keySet()) {
407             Node n = p.getNode();
408             Set<NodeConnector> pSet = res.get(n);
409             if (pSet == null) {
410                 // Create the HashSet if null
411                 pSet = new HashSet<NodeConnector>();
412                 res.put(n, pSet);
413             }
414
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.
418             pSet.add(p);
419         }
420
421         return (res);
422     }
423
424     @Override
425     public Host getHostAttachedToNodeConnector(NodeConnector p) {
426         if (this.hostsDB == null) {
427             return null;
428         }
429         if (this.hostsDB.get(p) == null)
430             return null;
431
432         return (this.hostsDB.get(p).getLeft());
433     }
434
435     @Override
436     public void updateHostLink(NodeConnector p, Host h, UpdateType t,
437             Set<Property> props) {
438         if (this.hostsDB == null) {
439             return;
440         }
441
442         switch (t) {
443         case ADDED:
444         case CHANGED:
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
448             if (props == null) {
449                 props = new HashSet<Property>();
450             } else {
451                 props = new HashSet<Property>(props);
452             }
453
454             this.hostsDB.put(p, new ImmutablePair(h, props));
455             break;
456         case REMOVED:
457             this.hostsDB.remove(p);
458             break;
459         }
460     }
461
462     private TopoEdgeUpdate edgeUpdate(Edge e, UpdateType type,
463             Set<Property> props) {
464         switch (type) {
465         case ADDED:
466             // Make sure the props are non-null
467             if (props == null) {
468                 props = (Set<Property>) new HashSet();
469             } else {
470                 // Copy the set so noone is going to change the content
471                 props = (Set<Property>) new HashSet(props);
472             }
473
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")) {
481                         found_create = true;
482                     }
483                 }
484             }
485
486             if (!found_create) {
487                 TimeStamp t = new TimeStamp(System.currentTimeMillis(),
488                         "creation");
489                 props.add(t);
490             }
491
492             // Now add this in the database eventually overriding
493             // something that may have been already existing
494             this.edgesDB.put(e, props);
495
496             // Now populate the DB of NodeConnectors
497             // NOTE WELL: properties are empty sets, not really needed
498             // for now.
499             // The DB only contains ISL ports
500             if (isISLink(e)) {
501                 this.nodeConnectorsDB.put(e.getHeadNodeConnector(),
502                         new HashSet<Property>());
503                 this.nodeConnectorsDB.put(e.getTailNodeConnector(),
504                         new HashSet<Property>());
505             }
506             log.trace("Edge {}  {}", e.toString(), type.name());
507             break;
508         case REMOVED:
509             // Now remove the edge from edgesDB
510             this.edgesDB.remove(e);
511
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());
522             break;
523         case CHANGED:
524             Set<Property> old_props = this.edgesDB.get(e);
525
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
529             // again
530             TimeStamp tc = null;
531             for (Property prop : old_props) {
532                 if (prop instanceof TimeStamp) {
533                     TimeStamp t = (TimeStamp) prop;
534                     if (t.getTimeStampName().equals("creation")) {
535                         tc = t;
536                     }
537                 }
538             }
539
540             // Now lets make sure new properties are non-null
541             // Make sure the props are non-null
542             if (props == null) {
543                 props = (Set<Property>) new HashSet();
544             } else {
545                 // Copy the set so noone is going to change the content
546                 props = (Set<Property>) new HashSet(props);
547             }
548
549             // Now lets remove the creation property if exist in the
550             // new props
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")) {
556                         i.remove();
557                     }
558                 }
559             }
560
561             // Now lets add the creation timestamp in it
562             if (tc != null) {
563                 props.add(tc);
564             }
565
566             // Finally update
567             this.edgesDB.put(e, props);
568             log.trace("Edge {}  {}", e.toString(), type.name());
569             break;
570         }
571         return new TopoEdgeUpdate(e, props, type);
572     }
573
574     @Override
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);
582             teuList.add(teu);
583         }
584
585         // Now update the listeners
586         for (ITopologyManagerAware s : this.topologyManagerAware) {
587             try {
588                 s.edgeUpdate(teuList);
589             } catch (Exception exc) {
590                 log.error("Exception on callback", exc);
591             }
592         }
593
594     }
595
596     private Edge getReverseLinkTuple(TopologyUserLinkConfig link) {
597         TopologyUserLinkConfig rLink = new TopologyUserLinkConfig(
598                 link.getName(), link.getDstNodeConnector(), link.getSrcNodeConnector());
599         return getLinkTuple(rLink);
600     }
601
602
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;
608         try {
609             linkTuple = new Edge(srcNodeConnector, dstNodeConnector);
610         } catch (Exception e) {
611         }
612         return linkTuple;
613     }
614
615     @Override
616     public ConcurrentMap<String, TopologyUserLinkConfig> getUserLinks() {
617         return userLinks;
618     }
619
620     @Override
621     public Status addUserLink(TopologyUserLinkConfig link) {
622         if (!link.isValid()) {
623             return new Status(StatusCode.BADREQUEST,
624                     "Configuration Invalid. Please check the parameters");
625         }
626         if (userLinks.get(link.getName()) != null) {
627             return new Status(StatusCode.CONFLICT, "Link with name : "
628                     + link.getName()
629                     + " already exists. Please use another name");
630         }
631         if (userLinks.containsValue(link)) {
632             return new Status(StatusCode.CONFLICT, "Link configuration exists");
633         }
634
635         link.setStatus(TopologyUserLinkConfig.STATUS.LINKDOWN);
636         userLinks.put(link.getName(), link);
637
638         Edge linkTuple = getLinkTuple(link);
639         if (linkTuple != null) {
640             if (!isProductionLink(linkTuple)) {
641                 edgeUpdate(linkTuple, UpdateType.ADDED, new HashSet<Property>());
642             }
643
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>());
649                 }
650             }
651         }
652         return new Status(StatusCode.SUCCESS, null);
653     }
654
655     @Override
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");
660         }
661
662         TopologyUserLinkConfig link = userLinks.get(linkName);
663
664         Edge linkTuple = getLinkTuple(link);
665         userLinks.remove(linkName);
666         if (linkTuple != null) {
667             if (!isProductionLink(linkTuple)) {
668                 edgeUpdate(linkTuple, UpdateType.REMOVED, null);
669             }
670
671             linkTuple = getReverseLinkTuple(link);
672             if ((linkTuple != null) && !isProductionLink(linkTuple)) {
673                 edgeUpdate(linkTuple, UpdateType.REMOVED, null);
674             }
675         }
676         return new Status(StatusCode.SUCCESS, null);
677     }
678
679     private void registerWithOSGIConsole() {
680         BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
681                 .getBundleContext();
682         bundleContext.registerService(CommandProvider.class.getName(), this,
683                 null);
684     }
685
686     @Override
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();
695     }
696
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));
704         }
705     }
706
707     public void _addUserLink(CommandInterpreter ci) {
708         String name = ci.nextArgument();
709         if ((name == null)) {
710             ci.println("Please enter a valid Name");
711             return;
712         }
713
714         String ncStr1 = ci.nextArgument();
715         if (ncStr1 == null) {
716             ci.println("Please enter two node connector strings");
717             return;
718         }
719         String ncStr2 = ci.nextArgument();
720         if (ncStr2 == null) {
721             ci.println("Please enter second node connector string");
722             return;
723         }
724
725         NodeConnector nc1 = NodeConnector.fromString(ncStr1);
726         if (nc1 == null) {
727             ci.println("Invalid input node connector 1 string: " + ncStr1);
728             return;
729         }
730         NodeConnector nc2 = NodeConnector.fromString(ncStr2);
731         if (nc2 == null) {
732             ci.println("Invalid input node connector 2 string: " + ncStr2);
733             return;
734         }
735
736         TopologyUserLinkConfig config = new TopologyUserLinkConfig(name, ncStr1, ncStr2);
737         ci.println(this.addUserLink(config));
738     }
739
740     public void _deleteUserLink(CommandInterpreter ci) {
741         String name = ci.nextArgument();
742         if ((name == null)) {
743             ci.println("Please enter a valid Name");
744             return;
745         }
746         this.deleteUserLink(name);
747     }
748
749     public void _printNodeEdges(CommandInterpreter ci) {
750         Map<Node, Set<Edge>> nodeEdges = getNodeEdges();
751         if (nodeEdges == null) {
752             return;
753         }
754         Set<Node> nodeSet = nodeEdges.keySet();
755         if (nodeSet == null) {
756             return;
757         }
758         ci.println("        Node                                         Edge");
759         for (Node node : nodeSet) {
760             Set<Edge> edgeSet = nodeEdges.get(node);
761             if (edgeSet == null) {
762                 continue;
763             }
764             for (Edge edge : edgeSet) {
765                 ci.println(node + "             " + edge);
766             }
767         }
768     }
769
770     @Override
771     public Object readObject(ObjectInputStream ois)
772             throws FileNotFoundException, IOException, ClassNotFoundException {
773         // TODO Auto-generated method stub
774         return ois.readObject();
775     }
776
777     @Override
778     public Status saveConfiguration() {
779         return saveConfig();
780     }
781
782     @Override
783     public void edgeOverUtilized(Edge edge) {
784         log.warn("Link Utilization above normal: {}", edge);
785     }
786
787     @Override
788     public void edgeUtilBackToNormal(Edge edge) {
789         log.warn("Link Utilization back to normal: {}", edge);
790     }
791
792 }