HA - Cache synch for Topology Manager
[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.Date;
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 import java.util.concurrent.CopyOnWriteArraySet;
27
28 import org.apache.commons.lang3.tuple.ImmutablePair;
29 import org.apache.felix.dm.Component;
30 import org.eclipse.osgi.framework.console.CommandInterpreter;
31 import org.eclipse.osgi.framework.console.CommandProvider;
32 import org.opendaylight.controller.clustering.services.CacheConfigException;
33 import org.opendaylight.controller.clustering.services.CacheExistException;
34 import org.opendaylight.controller.clustering.services.IClusterContainerServices;
35 import org.opendaylight.controller.clustering.services.IClusterServices;
36 import org.opendaylight.controller.configuration.IConfigurationContainerAware;
37 import org.opendaylight.controller.sal.core.Edge;
38 import org.opendaylight.controller.sal.core.Host;
39 import org.opendaylight.controller.sal.core.Node;
40 import org.opendaylight.controller.sal.core.NodeConnector;
41 import org.opendaylight.controller.sal.core.Property;
42 import org.opendaylight.controller.sal.core.TimeStamp;
43 import org.opendaylight.controller.sal.core.UpdateType;
44 import org.opendaylight.controller.sal.topology.IListenTopoUpdates;
45 import org.opendaylight.controller.sal.topology.ITopologyService;
46 import org.opendaylight.controller.sal.topology.TopoEdgeUpdate;
47 import org.opendaylight.controller.sal.utils.GlobalConstants;
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.sal.utils.StatusCode;
53 import org.opendaylight.controller.topologymanager.ITopologyManager;
54 import org.opendaylight.controller.topologymanager.ITopologyManagerAware;
55 import org.opendaylight.controller.topologymanager.TopologyUserLinkConfig;
56 import org.osgi.framework.BundleContext;
57 import org.osgi.framework.FrameworkUtil;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61 /**
62  * The class describes TopologyManager which is the central repository of the
63  * network topology. It provides service for applications to interact with
64  * topology database and notifies all the listeners of topology changes.
65  */
66 public class TopologyManagerImpl implements ITopologyManager,
67 IConfigurationContainerAware, IListenTopoUpdates, IObjectReader,
68 CommandProvider {
69     private static final Logger log = LoggerFactory.getLogger(TopologyManagerImpl.class);
70     private static final String SAVE = "Save";
71     private ITopologyService topoService;
72     private IClusterContainerServices clusterContainerService;
73     // DB of all the Edges with properties which constitute our topology
74     private ConcurrentMap<Edge, Set<Property>> edgesDB;
75     // DB of all NodeConnector which are part of ISL Edges, meaning they
76     // are connected to another NodeConnector on the other side of an ISL link.
77     // NodeConnector of a Production Edge is not part of this DB.
78     private ConcurrentMap<NodeConnector, Set<Property>> nodeConnectorsDB;
79     // DB of all the NodeConnectors with an Host attached to it
80     private ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>> hostsDB;
81     // Topology Manager Aware listeners
82     private Set<ITopologyManagerAware> topologyManagerAware =
83             new CopyOnWriteArraySet<ITopologyManagerAware>();;
84
85     private static String ROOT = GlobalConstants.STARTUPHOME.toString();
86     private String userLinksFileName;
87     private ConcurrentMap<String, TopologyUserLinkConfig> userLinksDB;
88     private ConcurrentMap<Long, String> configSaveEvent;
89
90
91     void nonClusterObjectCreate() {
92         edgesDB = new ConcurrentHashMap<Edge, Set<Property>>();
93         hostsDB = new ConcurrentHashMap<NodeConnector, ImmutablePair<Host, Set<Property>>>();
94         nodeConnectorsDB = new ConcurrentHashMap<NodeConnector, Set<Property>>();
95         userLinksDB = new ConcurrentHashMap<String, TopologyUserLinkConfig>();
96         configSaveEvent = new ConcurrentHashMap<Long, String>();
97     }
98
99     void setTopologyManagerAware(ITopologyManagerAware s) {
100         if (this.topologyManagerAware != null) {
101             log.debug("Adding ITopologyManagerAware: {}", s);
102             this.topologyManagerAware.add(s);
103         }
104     }
105
106     void unsetTopologyManagerAware(ITopologyManagerAware s) {
107         if (this.topologyManagerAware != null) {
108             log.debug("Removing ITopologyManagerAware: {}", s);
109             this.topologyManagerAware.remove(s);
110         }
111     }
112
113     void setTopoService(ITopologyService s) {
114         log.debug("Adding ITopologyService: {}", s);
115         this.topoService = s;
116     }
117
118     void unsetTopoService(ITopologyService s) {
119         if (this.topoService == s) {
120             log.debug("Removing ITopologyService: {}", s);
121             this.topoService = null;
122         }
123     }
124
125     void setClusterContainerService(IClusterContainerServices s) {
126         log.debug("Cluster Service set");
127         this.clusterContainerService = s;
128     }
129
130     void unsetClusterContainerService(IClusterContainerServices s) {
131         if (this.clusterContainerService == s) {
132             log.debug("Cluster Service removed!");
133             this.clusterContainerService = null;
134         }
135     }
136
137     /**
138      * Function called by the dependency manager when all the required
139      * dependencies are satisfied
140      *
141      */
142     void init(Component c) {
143
144         allocateCaches();
145         retrieveCaches();
146
147         String containerName = null;
148         Dictionary<?, ?> props = c.getServiceProperties();
149         if (props != null) {
150             containerName = (String) props.get("containerName");
151         } else {
152             // In the Global instance case the containerName is empty
153             containerName = "UNKNOWN";
154         }
155
156         userLinksFileName = ROOT + "userTopology_" + containerName + ".conf";
157         registerWithOSGIConsole();
158         loadConfiguration();
159     }
160
161     @SuppressWarnings({ "unchecked", "deprecation" })
162     private void allocateCaches(){
163         if (this.clusterContainerService == null) {
164             nonClusterObjectCreate();
165             log.error("Cluster Services unavailable, allocated non-cluster caches!");
166             return;
167         }
168
169         try {
170             this.edgesDB = (ConcurrentMap<Edge, Set<Property>>) this.clusterContainerService.createCache(
171                     "topologymanager.edgesDB", EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
172         } catch (CacheExistException cee) {
173             log.debug("topologymanager.edgesDB Cache already exists - destroy and recreate if needed");
174         } catch (CacheConfigException cce) {
175             log.error("topologymanager.edgesDB Cache configuration invalid - check cache mode");
176         }
177
178         try {
179             this.hostsDB = (ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>>) this.clusterContainerService
180                     .createCache("topologymanager.hostsDB", EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
181         } catch (CacheExistException cee) {
182             log.debug("topologymanager.hostsDB Cache already exists - destroy and recreate if needed");
183         } catch (CacheConfigException cce) {
184             log.error("topologymanager.hostsDB Cache configuration invalid - check cache mode");
185         }
186
187         try {
188             this.nodeConnectorsDB = (ConcurrentMap<NodeConnector, Set<Property>>) this.clusterContainerService
189                     .createCache("topologymanager.nodeConnectorDB", EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
190         } catch (CacheExistException cee) {
191             log.debug("topologymanager.nodeConnectorDB Cache already exists - destroy and recreate if needed");
192         } catch (CacheConfigException cce) {
193             log.error("topologymanager.nodeConnectorDB Cache configuration invalid - check cache mode");
194         }
195
196         try {
197             this.userLinksDB = (ConcurrentMap<String, TopologyUserLinkConfig>) this.clusterContainerService
198                     .createCache("topologymanager.userLinksDB", EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
199         } catch (CacheExistException cee) {
200             log.debug("topologymanager.userLinksDB Cache already exists - destroy and recreate if needed");
201         } catch (CacheConfigException cce) {
202             log.error("topologymanager.userLinksDB Cache configuration invalid - check cache mode");
203         }
204
205         try {
206             this.configSaveEvent = (ConcurrentMap<Long, String>) this.clusterContainerService
207                     .createCache("topologymanager.configSaveEvent", EnumSet.of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
208         } catch (CacheExistException cee) {
209             log.debug("topologymanager.configSaveEvent Cache already exists - destroy and recreate if needed");
210         } catch (CacheConfigException cce) {
211             log.error("topologymanager.configSaveEvent Cache configuration invalid - check cache mode");
212         }
213
214     }
215
216     @SuppressWarnings({ "unchecked", "deprecation" })
217     private void retrieveCaches() {
218         if (this.clusterContainerService == null) {
219             log.error("Cluster Services is null, can't retrieve caches.");
220             return;
221         }
222
223         this.edgesDB = (ConcurrentMap<Edge, Set<Property>>) this.clusterContainerService
224                 .getCache("topologymanager.edgesDB");
225         if (edgesDB == null) {
226             log.error("Failed to get cache for topologymanager.edgesDB");
227         }
228
229         this.hostsDB = (ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>>) this.clusterContainerService
230                 .getCache("topologymanager.hostsDB");
231         if (hostsDB == null) {
232             log.error("Failed to get cache for topologymanager.hostsDB");
233         }
234
235         this.nodeConnectorsDB = (ConcurrentMap<NodeConnector, Set<Property>>) this.clusterContainerService
236                 .getCache("topologymanager.nodeConnectorDB");
237         if (nodeConnectorsDB == null) {
238             log.error("Failed to get cache for topologymanager.nodeConnectorDB");
239         }
240
241         this.userLinksDB = (ConcurrentMap<String, TopologyUserLinkConfig>) this.clusterContainerService
242                 .getCache("topologymanager.userLinksDB");
243         if (userLinksDB == null) {
244             log.error("Failed to get cache for topologymanager.userLinksDB");
245         }
246
247         this.configSaveEvent = (ConcurrentMap<Long, String>) this.clusterContainerService
248                 .getCache("topologymanager.configSaveEvent");
249         if (configSaveEvent == null) {
250             log.error("Failed to get cache for topologymanager.configSaveEvent");
251         }
252
253     }
254
255     /**
256      * Function called after the topology manager has registered the service in
257      * OSGi service registry.
258      *
259      */
260     void started() {
261         // SollicitRefresh MUST be called here else if called at init
262         // time it may sollicit refresh too soon.
263         log.debug("Sollicit topology refresh");
264         topoService.sollicitRefresh();
265     }
266
267     /**
268      * Function called by the dependency manager when at least one dependency
269      * become unsatisfied or when the component is shutting down because for
270      * example bundle is being stopped.
271      *
272      */
273     void destroy() {
274     }
275
276     @SuppressWarnings("unchecked")
277     private void loadConfiguration() {
278         ObjectReader objReader = new ObjectReader();
279         ConcurrentMap<String, TopologyUserLinkConfig> confList =
280                 (ConcurrentMap<String, TopologyUserLinkConfig>) objReader.read(this, userLinksFileName);
281
282         if (confList != null) {
283             for (TopologyUserLinkConfig conf : confList.values()) {
284                 addUserLink(conf);
285             }
286         }
287     }
288
289     @Override
290     public Status saveConfig() {
291         // Publish the save config event to the cluster
292         configSaveEvent.put(new Date().getTime(), SAVE );
293         return saveConfigInternal();
294     }
295
296     public Status saveConfigInternal() {
297         ObjectWriter objWriter = new ObjectWriter();
298
299         Status saveStatus = objWriter.write(
300                 new ConcurrentHashMap<String, TopologyUserLinkConfig>(userLinksDB), userLinksFileName);
301
302         if (! saveStatus.isSuccess()) {
303             return new Status(StatusCode.INTERNALERROR, "Topology save failed: " + saveStatus.getDescription());
304         }
305         return saveStatus;
306     }
307
308     @Override
309     public Map<Node, Set<Edge>> getNodeEdges() {
310         if (this.edgesDB == null) {
311             return null;
312         }
313
314         Map<Node, Set<Edge>> res = new HashMap<Node, Set<Edge>>();
315         for (Edge edge : this.edgesDB.keySet()) {
316             // Lets analyze the tail
317             Node node = edge.getTailNodeConnector().getNode();
318             Set<Edge> nodeEdges = res.get(node);
319             if (nodeEdges == null) {
320                 nodeEdges = new HashSet<Edge>();
321                 res.put(node, nodeEdges);
322             }
323             nodeEdges.add(edge);
324
325             // Lets analyze the head
326             node = edge.getHeadNodeConnector().getNode();
327             nodeEdges = res.get(node);
328             if (nodeEdges == null) {
329                 nodeEdges = new HashSet<Edge>();
330                 res.put(node, nodeEdges);
331             }
332             nodeEdges.add(edge);
333         }
334
335         return res;
336     }
337
338     @Override
339     public boolean isInternal(NodeConnector p) {
340         if (this.nodeConnectorsDB == null) {
341             return false;
342         }
343
344         // This is an internal NodeConnector if is connected to
345         // another Node i.e it's part of the nodeConnectorsDB
346         return (this.nodeConnectorsDB.get(p) != null);
347     }
348
349     /**
350      * This method returns true if the edge is an ISL link.
351      *
352      * @param e
353      *            The edge
354      * @return true if it is an ISL link
355      */
356     public boolean isISLink(Edge e) {
357         return (!isProductionLink(e));
358     }
359
360     /**
361      * This method returns true if the edge is a production link.
362      *
363      * @param e
364      *            The edge
365      * @return true if it is a production link
366      */
367     public boolean isProductionLink(Edge e) {
368         return (e.getHeadNodeConnector().getType().equals(NodeConnector.NodeConnectorIDType.PRODUCTION)
369                 || e.getTailNodeConnector().getType().equals(NodeConnector.NodeConnectorIDType.PRODUCTION));
370     }
371
372     /**
373      * The Map returned is a copy of the current topology hence if the topology
374      * changes the copy doesn't
375      *
376      * @return A Map representing the current topology expressed as edges of the
377      *         network
378      */
379     @Override
380     public Map<Edge, Set<Property>> getEdges() {
381         if (this.edgesDB == null) {
382             return null;
383         }
384
385         Map<Edge, Set<Property>> edgeMap = new HashMap<Edge, Set<Property>>();
386         Set<Property> props;
387         for (Map.Entry<Edge, Set<Property>> edgeEntry : edgesDB.entrySet()) {
388             // Sets of props are copied because the composition of
389             // those properties could change with time
390             props = new HashSet<Property>(edgeEntry.getValue());
391             // We can simply reuse the key because the object is
392             // immutable so doesn't really matter that we are
393             // referencing the only owned by a different table, the
394             // meaning is the same because doesn't change with time.
395             edgeMap.put(edgeEntry.getKey(), props);
396         }
397
398         return edgeMap;
399     }
400
401     @Override
402     public Set<NodeConnector> getNodeConnectorWithHost() {
403         if (this.hostsDB == null) {
404             return null;
405         }
406
407         return (new HashSet<NodeConnector>(this.hostsDB.keySet()));
408     }
409
410     @Override
411     public Map<Node, Set<NodeConnector>> getNodesWithNodeConnectorHost() {
412         if (this.hostsDB == null) {
413             return null;
414         }
415         HashMap<Node, Set<NodeConnector>> res = new HashMap<Node, Set<NodeConnector>>();
416         Node node;
417         Set<NodeConnector> portSet;
418         for (NodeConnector nc : this.hostsDB.keySet()) {
419             node = nc.getNode();
420             portSet = res.get(node);
421             if (portSet == null) {
422                 // Create the HashSet if null
423                 portSet = new HashSet<NodeConnector>();
424                 res.put(node, portSet);
425             }
426
427             // Keep updating the HashSet, given this is not a
428             // clustered map we can just update the set without
429             // worrying to update the hashmap.
430             portSet.add(nc);
431         }
432
433         return (res);
434     }
435
436     @Override
437     public Host getHostAttachedToNodeConnector(NodeConnector port) {
438         ImmutablePair<Host, Set<Property>> host;
439         if (this.hostsDB == null || (host = this.hostsDB.get(port)) == null) {
440             return null;
441         }
442         return host.getLeft();
443     }
444
445     @Override
446     public void updateHostLink(NodeConnector port, Host h, UpdateType t, Set<Property> props) {
447
448         // Clone the property set in case non null else just
449         // create an empty one. Caches allocated via infinispan
450         // don't allow null values
451         if (props == null) {
452             props = new HashSet<Property>();
453         } else {
454             props = new HashSet<Property>(props);
455         }
456         ImmutablePair<Host, Set<Property>> thisHost = new ImmutablePair<Host, Set<Property>>(h, props);
457
458         switch (t) {
459         case ADDED:
460         case CHANGED:
461             this.hostsDB.put(port, thisHost);
462             break;
463         case REMOVED:
464             //remove only if hasn't been concurrently modified
465             this.hostsDB.remove(port, thisHost);
466             break;
467         }
468     }
469
470     private TopoEdgeUpdate edgeUpdate(Edge e, UpdateType type, Set<Property> props) {
471         switch (type) {
472         case ADDED:
473             // Make sure the props are non-null
474             if (props == null) {
475                 props = new HashSet<Property>();
476             } else {
477                 props = new HashSet<Property>(props);
478             }
479
480             //in case of node switch-over to a different cluster controller,
481             //let's retain edge props
482             Set<Property> currentProps = this.edgesDB.get(e);
483             if (currentProps != null){
484                 props.addAll(currentProps);
485             }
486
487             // Now make sure there is the creation timestamp for the
488             // edge, if not there, stamp with the first update
489             boolean found_create = false;
490             for (Property prop : props) {
491                 if (prop instanceof TimeStamp) {
492                     TimeStamp t = (TimeStamp) prop;
493                     if (t.getTimeStampName().equals("creation")) {
494                         found_create = true;
495                         break;
496                     }
497                 }
498             }
499
500             if (!found_create) {
501                 TimeStamp t = new TimeStamp(System.currentTimeMillis(), "creation");
502                 props.add(t);
503             }
504
505             // Now add this in the database eventually overriding
506             // something that may have been already existing
507             this.edgesDB.put(e, props);
508
509             // Now populate the DB of NodeConnectors
510             // NOTE WELL: properties are empty sets, not really needed
511             // for now.
512             // The DB only contains ISL ports
513             if (isISLink(e)) {
514                 this.nodeConnectorsDB.put(e.getHeadNodeConnector(), new HashSet<Property>(1));
515                 this.nodeConnectorsDB.put(e.getTailNodeConnector(), new HashSet<Property>(1));
516             }
517             log.trace("Edge {}  {}", e.toString(), type.name());
518             break;
519         case REMOVED:
520             // Now remove the edge from edgesDB
521             this.edgesDB.remove(e);
522
523             // Now lets update the NodeConnectors DB, the assumption
524             // here is that two NodeConnector are exclusively
525             // connected by 1 and only 1 edge, this is reasonable in
526             // the same plug (virtual of phisical) we can assume two
527             // cables won't be plugged. This could break only in case
528             // of devices in the middle that acts as hubs, but it
529             // should be safe to assume that won't happen.
530             this.nodeConnectorsDB.remove(e.getHeadNodeConnector());
531             this.nodeConnectorsDB.remove(e.getTailNodeConnector());
532             log.trace("Edge {}  {}", e.toString(), type.name());
533             break;
534         case CHANGED:
535             Set<Property> oldProps = this.edgesDB.get(e);
536
537             // When property changes lets make sure we can change it
538             // all except the creation time stamp because that should
539             // be changed only when the edge is destroyed and created
540             // again
541             TimeStamp timeStamp = null;
542             for (Property prop : oldProps) {
543                 if (prop instanceof TimeStamp) {
544                     TimeStamp tsProp = (TimeStamp) prop;
545                     if (tsProp.getTimeStampName().equals("creation")) {
546                         timeStamp = tsProp;
547                         break;
548                     }
549                 }
550             }
551
552             // Now lets make sure new properties are non-null
553             if (props == null) {
554                 props = new HashSet<Property>();
555             } else {
556                 // Copy the set so noone is going to change the content
557                 props = new HashSet<Property>(props);
558             }
559
560             // Now lets remove the creation property if exist in the
561             // new props
562             for (Iterator<Property> i = props.iterator(); i.hasNext();) {
563                 Property prop = i.next();
564                 if (prop instanceof TimeStamp) {
565                     TimeStamp t = (TimeStamp) prop;
566                     if (t.getTimeStampName().equals("creation")) {
567                         i.remove();
568                         break;
569                     }
570                 }
571             }
572
573             // Now lets add the creation timestamp in it
574             if (timeStamp != null) {
575                 props.add(timeStamp);
576             }
577
578             // Finally update
579             this.edgesDB.put(e, props);
580             log.trace("Edge {}  {}", e.toString(), type.name());
581             break;
582         }
583         return new TopoEdgeUpdate(e, props, type);
584     }
585
586     @Override
587     public void edgeUpdate(List<TopoEdgeUpdate> topoedgeupdateList) {
588         List<TopoEdgeUpdate> teuList = new ArrayList<TopoEdgeUpdate>();
589         for (int i = 0; i < topoedgeupdateList.size(); i++) {
590             Edge e = topoedgeupdateList.get(i).getEdge();
591             Set<Property> p = topoedgeupdateList.get(i).getProperty();
592             UpdateType type = topoedgeupdateList.get(i).getUpdateType();
593             TopoEdgeUpdate teu = edgeUpdate(e, type, p);
594             teuList.add(teu);
595         }
596
597         // Now update the listeners
598         for (ITopologyManagerAware s : this.topologyManagerAware) {
599             try {
600                 s.edgeUpdate(teuList);
601             } catch (Exception exc) {
602                 log.error("Exception on edge update:", exc);
603             }
604         }
605
606     }
607
608     private Edge getReverseLinkTuple(TopologyUserLinkConfig link) {
609         TopologyUserLinkConfig rLink = new TopologyUserLinkConfig(
610                 link.getName(), link.getDstNodeConnector(), link.getSrcNodeConnector());
611         return getLinkTuple(rLink);
612     }
613
614
615     private Edge getLinkTuple(TopologyUserLinkConfig link) {
616         NodeConnector srcNodeConnector = NodeConnector.fromString(link.getSrcNodeConnector());
617         NodeConnector dstNodeConnector = NodeConnector.fromString(link.getDstNodeConnector());
618         try {
619             return new Edge(srcNodeConnector, dstNodeConnector);
620         } catch (Exception e) {
621             return null;
622         }
623     }
624
625     @Override
626     public ConcurrentMap<String, TopologyUserLinkConfig> getUserLinks() {
627         return new ConcurrentHashMap<String, TopologyUserLinkConfig>(userLinksDB);
628     }
629
630     @Override
631     public Status addUserLink(TopologyUserLinkConfig userLink) {
632         if (!userLink.isValid()) {
633             return new Status(StatusCode.BADREQUEST,
634                     "User link configuration invalid.");
635         }
636         userLink.setStatus(TopologyUserLinkConfig.STATUS.LINKDOWN);
637
638         //Check if this link already configured
639         //NOTE: infinispan cache doesn't support Map.containsValue()
640         // (which is linear time in most ConcurrentMap impl anyway)
641         for (TopologyUserLinkConfig existingLink : userLinksDB.values()) {
642             if (existingLink.equals(userLink)) {
643                 return new Status(StatusCode.CONFLICT, "Link configuration exists");
644             }
645         }
646         //attempt put, if mapping for this key already existed return conflict
647         if (userLinksDB.putIfAbsent(userLink.getName(), userLink) != null) {
648             return new Status(StatusCode.CONFLICT, "Link with name : " + userLink.getName()
649                     + " already exists. Please use another name");
650         }
651
652         Edge linkTuple = getLinkTuple(userLink);
653         if (linkTuple != null) {
654             if (!isProductionLink(linkTuple)) {
655                 edgeUpdate(linkTuple, UpdateType.ADDED, new HashSet<Property>());
656             }
657
658             linkTuple = getReverseLinkTuple(userLink);
659             if (linkTuple != null) {
660                 userLink.setStatus(TopologyUserLinkConfig.STATUS.SUCCESS);
661                 if (!isProductionLink(linkTuple)) {
662                     edgeUpdate(linkTuple, UpdateType.ADDED, new HashSet<Property>());
663                 }
664             }
665         }
666         return new Status(StatusCode.SUCCESS);
667     }
668
669     @Override
670     public Status deleteUserLink(String linkName) {
671         if (linkName == null) {
672             return new Status(StatusCode.BADREQUEST, "User link name cannot be null.");
673         }
674
675         TopologyUserLinkConfig link = userLinksDB.remove(linkName);
676         Edge linkTuple;
677         if (link != null && (linkTuple = getLinkTuple(link)) != null) {
678             if (! isProductionLink(linkTuple)) {
679                 edgeUpdate(linkTuple, UpdateType.REMOVED, null);
680             }
681
682             linkTuple = getReverseLinkTuple(link);
683             if (! isProductionLink(linkTuple)) {
684                 edgeUpdate(linkTuple, UpdateType.REMOVED, null);
685             }
686         }
687         return new Status(StatusCode.SUCCESS);
688     }
689
690     private void registerWithOSGIConsole() {
691         BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
692                 .getBundleContext();
693         bundleContext.registerService(CommandProvider.class.getName(), this,
694                 null);
695     }
696
697     @Override
698     public String getHelp() {
699         StringBuffer help = new StringBuffer();
700         help.append("---Topology Manager---\n");
701         help.append("\t addUserLink <name> <node connector string> <node connector string>\n");
702         help.append("\t deleteUserLink <name>\n");
703         help.append("\t printUserLink\n");
704         help.append("\t printNodeEdges\n");
705         return help.toString();
706     }
707
708     public void _printUserLink(CommandInterpreter ci) {
709         for (String name : this.userLinksDB.keySet()) {
710             TopologyUserLinkConfig linkConfig = userLinksDB.get(name);
711             ci.println("Name : " + name);
712             ci.println(linkConfig);
713             ci.println("Edge " + getLinkTuple(linkConfig));
714             ci.println("Reverse Edge " + getReverseLinkTuple(linkConfig));
715         }
716     }
717
718     public void _addUserLink(CommandInterpreter ci) {
719         String name = ci.nextArgument();
720         if ((name == null)) {
721             ci.println("Please enter a valid Name");
722             return;
723         }
724
725         String ncStr1 = ci.nextArgument();
726         if (ncStr1 == null) {
727             ci.println("Please enter two node connector strings");
728             return;
729         }
730         String ncStr2 = ci.nextArgument();
731         if (ncStr2 == null) {
732             ci.println("Please enter second node connector string");
733             return;
734         }
735
736         NodeConnector nc1 = NodeConnector.fromString(ncStr1);
737         if (nc1 == null) {
738             ci.println("Invalid input node connector 1 string: " + ncStr1);
739             return;
740         }
741         NodeConnector nc2 = NodeConnector.fromString(ncStr2);
742         if (nc2 == null) {
743             ci.println("Invalid input node connector 2 string: " + ncStr2);
744             return;
745         }
746
747         TopologyUserLinkConfig config = new TopologyUserLinkConfig(name, ncStr1, ncStr2);
748         ci.println(this.addUserLink(config));
749     }
750
751     public void _deleteUserLink(CommandInterpreter ci) {
752         String name = ci.nextArgument();
753         if ((name == null)) {
754             ci.println("Please enter a valid Name");
755             return;
756         }
757         this.deleteUserLink(name);
758     }
759
760     public void _printNodeEdges(CommandInterpreter ci) {
761         Map<Node, Set<Edge>> nodeEdges = getNodeEdges();
762         if (nodeEdges == null) {
763             return;
764         }
765         Set<Node> nodeSet = nodeEdges.keySet();
766         if (nodeSet == null) {
767             return;
768         }
769         ci.println("        Node                                         Edge");
770         for (Node node : nodeSet) {
771             Set<Edge> edgeSet = nodeEdges.get(node);
772             if (edgeSet == null) {
773                 continue;
774             }
775             for (Edge edge : edgeSet) {
776                 ci.println(node + "             " + edge);
777             }
778         }
779     }
780
781     @Override
782     public Object readObject(ObjectInputStream ois)
783             throws FileNotFoundException, IOException, ClassNotFoundException {
784         return ois.readObject();
785     }
786
787     @Override
788     public Status saveConfiguration() {
789         return saveConfig();
790     }
791
792     @Override
793     public void edgeOverUtilized(Edge edge) {
794         log.warn("Link Utilization above normal: {}", edge);
795     }
796
797     @Override
798     public void edgeUtilBackToNormal(Edge edge) {
799         log.warn("Link Utilization back to normal: {}", edge);
800     }
801
802 }