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.NodeConnector.NodeConnectorIDType;
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.core.Node.NodeIDType;
45 import org.opendaylight.controller.sal.topology.IListenTopoUpdates;
46 import org.opendaylight.controller.sal.topology.ITopologyService;
47 import org.opendaylight.controller.sal.utils.StatusCode;
48 import org.opendaylight.controller.sal.utils.GlobalConstants;
49 import org.opendaylight.controller.sal.utils.IObjectReader;
50 import org.opendaylight.controller.sal.utils.ObjectReader;
51 import org.opendaylight.controller.sal.utils.ObjectWriter;
52 import org.opendaylight.controller.sal.utils.Status;
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;
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.
66 public class TopologyManagerImpl implements ITopologyManager,
67 IConfigurationContainerAware, IListenTopoUpdates, IObjectReader,
69 private static final Logger log = LoggerFactory
70 .getLogger(TopologyManagerImpl.class);
71 private ITopologyService topoService = null;
72 private IClusterContainerServices clusterContainerService = null;
73 // DB of all the Edges with properties which constitute our topology
74 private ConcurrentMap<Edge, Set<Property>> edgesDB = null;
75 // DB of all NodeConnector which are part of Edges, meaning they
76 // are connected to another NodeConnector on the other side
77 private ConcurrentMap<NodeConnector, Set<Property>> nodeConnectorsDB = null;
78 // DB of all the NodeConnectors with an Host attached to it
79 private ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>> hostsDB = null;
80 // Topology Manager Aware listeners
81 private Set<ITopologyManagerAware> topologyManagerAware = Collections
82 .synchronizedSet(new HashSet<ITopologyManagerAware>());
84 private static String ROOT = GlobalConstants.STARTUPHOME.toString();
85 private String userLinksFileName = null;
86 private ConcurrentMap<String, TopologyUserLinkConfig> userLinks;
88 void nonClusterObjectCreate() {
89 edgesDB = new ConcurrentHashMap<Edge, Set<Property>>();
90 hostsDB = new ConcurrentHashMap<NodeConnector, ImmutablePair<Host, Set<Property>>>();
91 userLinks = new ConcurrentHashMap<String, TopologyUserLinkConfig>();
92 nodeConnectorsDB = new ConcurrentHashMap<NodeConnector, Set<Property>>();
96 void setTopologyManagerAware(ITopologyManagerAware s) {
97 if (this.topologyManagerAware != null) {
98 log.debug("Adding ITopologyManagerAware: " + s);
99 this.topologyManagerAware.add(s);
103 void unsetTopologyManagerAware(ITopologyManagerAware s) {
104 if (this.topologyManagerAware != null) {
105 this.topologyManagerAware.remove(s);
109 void setTopoService(ITopologyService s) {
110 this.topoService = s;
113 void unsetTopoService(ITopologyService s) {
114 if (this.topoService == s) {
115 this.topoService = null;
119 void setClusterContainerService(IClusterContainerServices s) {
120 log.debug("Cluster Service set");
121 this.clusterContainerService = s;
124 void unsetClusterContainerService(IClusterContainerServices s) {
125 if (this.clusterContainerService == s) {
126 log.debug("Cluster Service removed!");
127 this.clusterContainerService = null;
132 * Function called by the dependency manager when all the required
133 * dependencies are satisfied
136 void init(Component c) {
137 String containerName = null;
138 Dictionary props = c.getServiceProperties();
140 containerName = (String) props.get("containerName");
142 // In the Global instance case the containerName is empty
143 containerName = "UNKNOWN";
146 if (this.clusterContainerService == null) {
147 log.error("Cluster Services is null, not expected!");
151 if (this.topoService == null) {
152 log.error("Topology Services is null, not expected!");
157 this.edgesDB = (ConcurrentMap<Edge, Set<Property>>) this.clusterContainerService
158 .createCache("topologymanager.edgesDB", EnumSet
159 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
160 } catch (CacheExistException cee) {
161 log.error("topologymanager.edgesDB Cache already exists - "
162 + "destroy and recreate if needed");
163 } catch (CacheConfigException cce) {
164 log.error("topologymanager.edgesDB Cache configuration invalid - "
165 + "check cache mode");
169 this.hostsDB = (ConcurrentMap<NodeConnector, ImmutablePair<Host, Set<Property>>>) this.clusterContainerService
170 .createCache("topologymanager.hostsDB", EnumSet
171 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
172 } catch (CacheExistException cee) {
173 log.error("topologymanager.hostsDB Cache already exists - "
174 + "destroy and recreate if needed");
175 } catch (CacheConfigException cce) {
176 log.error("topologymanager.hostsDB Cache configuration invalid - "
177 + "check cache mode");
181 this.nodeConnectorsDB = (ConcurrentMap<NodeConnector, Set<Property>>) this.clusterContainerService
182 .createCache("topologymanager.nodeConnectorDB", EnumSet
183 .of(IClusterServices.cacheMode.NON_TRANSACTIONAL));
184 } catch (CacheExistException cee) {
185 log.error("topologymanager.nodeConnectorDB Cache already exists"
186 + " - destroy and recreate if needed");
187 } catch (CacheConfigException cce) {
188 log.error("topologymanager.nodeConnectorDB Cache configuration "
189 + "invalid - check cache mode");
192 userLinks = new ConcurrentHashMap<String, TopologyUserLinkConfig>();
194 userLinksFileName = ROOT + "userTopology_" + containerName + ".conf";
195 registerWithOSGIConsole();
200 * Function called after the topology manager has registered the
201 * service in OSGi service registry.
205 // SollicitRefresh MUST be called here else if called at init
206 // time it may sollicit refresh too soon.
207 log.debug("Sollicit topology refresh");
208 topoService.sollicitRefresh();
212 * Function called by the dependency manager when at least one
213 * dependency become unsatisfied or when the component is shutting
214 * down because for example bundle is being stopped.
218 if (this.clusterContainerService == null) {
219 log.error("Cluster Services is null, not expected!");
222 this.nodeConnectorsDB = null;
225 this.clusterContainerService.destroyCache("topologymanager.edgesDB");
227 this.clusterContainerService.destroyCache("topologymanager.hostsDB");
229 this.clusterContainerService
230 .destroyCache("topologymanager.nodeConnectorDB");
231 this.nodeConnectorsDB = null;
232 log.debug("Topology Manager DB DE-allocated");
235 @SuppressWarnings("unchecked")
236 private void loadConfiguration() {
237 ObjectReader objReader = new ObjectReader();
238 ConcurrentMap<String, TopologyUserLinkConfig> confList = (ConcurrentMap<String, TopologyUserLinkConfig>) objReader
239 .read(this, userLinksFileName);
241 if (confList == null) {
245 for (TopologyUserLinkConfig conf : confList.values()) {
251 public Status saveConfig() {
252 // Publish the save config event to the cluster nodes
254 * Get the CLUSTERING SERVICES WORKING BEFORE TRYING THIS
256 configSaveEvent.put(new Date().getTime(), SAVE);
258 return saveConfigInternal();
261 public Status saveConfigInternal() {
263 ObjectWriter objWriter = new ObjectWriter();
265 retS = objWriter.write(
266 new ConcurrentHashMap<String, TopologyUserLinkConfig>(
267 userLinks), userLinksFileName);
269 if (retS.isSuccess()) {
272 return new Status(StatusCode.INTERNALERROR, "Save failed");
277 public Map<Node, Set<Edge>> getNodeEdges() {
278 if (this.edgesDB == null) {
282 HashMap<Node, Set<Edge>> res = new HashMap<Node, Set<Edge>>();
283 for (Edge key : this.edgesDB.keySet()) {
284 // Lets analyze the tail
285 Node node = key.getTailNodeConnector().getNode();
286 Set<Edge> nodeEdges = res.get(node);
287 if (nodeEdges == null) {
288 nodeEdges = new HashSet<Edge>();
291 // We need to re-add to the MAP even if the element was
292 // already there so in case of clustered services the map
293 // gets updated in the cluster
294 res.put(node, nodeEdges);
296 // Lets analyze the head
297 node = key.getHeadNodeConnector().getNode();
298 nodeEdges = res.get(node);
299 if (nodeEdges == null) {
300 nodeEdges = new HashSet<Edge>();
303 // We need to re-add to the MAP even if the element was
304 // already there so in case of clustered services the map
305 // gets updated in the cluster
306 res.put(node, nodeEdges);
313 public boolean isInternal(NodeConnector p) {
314 if (this.nodeConnectorsDB == null) {
318 // This is an internal NodeConnector if is connected to
319 // another Node i.e it's part of the nodeConnectorsDB
320 return (this.nodeConnectorsDB.get(p) != null);
324 * The Map returned is a copy of the current topology hence if the
325 * topology changes the copy doesn't
327 * @return A Map representing the current topology expressed as
328 * edges of the network
331 public Map<Edge, Set<Property>> getEdges() {
332 if (this.edgesDB == null) {
336 HashMap<Edge, Set<Property>> res = new HashMap<Edge, Set<Property>>();
337 for (Edge key : this.edgesDB.keySet()) {
338 // Sets of props are copied because the composition of
339 // those properties could change with time
340 HashSet<Property> prop = new HashSet<Property>(this.edgesDB
342 // We can simply reuse the key because the object is
343 // immutable so doesn't really matter that we are
344 // referencing the only owned by a different table, the
345 // meaning is the same because doesn't change with time.
352 // TODO remove with spring-dm removal
354 * @param set the topologyAware to set
356 public void setTopologyAware(Set<Object> set) {
357 for (Object s : set) {
358 setTopologyManagerAware((ITopologyManagerAware) s);
363 public Set<NodeConnector> getNodeConnectorWithHost() {
364 if (this.hostsDB == null) {
368 return (this.hostsDB.keySet());
372 public Map<Node, Set<NodeConnector>> getNodesWithNodeConnectorHost() {
373 if (this.hostsDB == null) {
376 HashMap<Node, Set<NodeConnector>> res = new HashMap<Node, Set<NodeConnector>>();
378 for (NodeConnector p : this.hostsDB.keySet()) {
379 Node n = p.getNode();
380 Set<NodeConnector> pSet = res.get(n);
382 // Create the HashSet if null
383 pSet = new HashSet<NodeConnector>();
387 // Keep updating the HashSet, given this is not a
388 // clustered map we can just update the set without
389 // worrying to update the hashmap.
397 public Host getHostAttachedToNodeConnector(NodeConnector p) {
398 if (this.hostsDB == null) {
402 return (this.hostsDB.get(p).getLeft());
406 public void updateHostLink(NodeConnector p, Host h, UpdateType t,
407 Set<Property> props) {
408 if (this.hostsDB == null) {
415 // Clone the property set in case non null else just
416 // create an empty one. Caches allocated via infinispan
417 // don't allow null values
419 props = new HashSet<Property>();
421 props = new HashSet<Property>(props);
424 this.hostsDB.put(p, new ImmutablePair(h, props));
427 this.hostsDB.remove(p);
433 public void edgeUpdate(Edge e, UpdateType type, Set<Property> props) {
436 // Make sure the props are non-null
438 props = (Set<Property>) new HashSet();
440 // Copy the set so noone is going to change the content
441 props = (Set<Property>) new HashSet(props);
444 // Now make sure thre is the creation timestamp for the
445 // edge, if not there timestamp with the first update
446 boolean found_create = false;
447 for (Property prop : props) {
448 if (prop instanceof TimeStamp) {
449 TimeStamp t = (TimeStamp) prop;
450 if (t.getTimeStampName().equals("creation")) {
457 TimeStamp t = new TimeStamp(System.currentTimeMillis(),
462 // Now add this in the database eventually overriding
463 // something that may have been already existing
464 this.edgesDB.put(e, props);
466 // Now populate the DB of NodeConnectors
467 // NOTE WELL: properties are empy sets, not really needed
469 this.nodeConnectorsDB.put(e.getHeadNodeConnector(),
470 new HashSet<Property>());
471 this.nodeConnectorsDB.put(e.getTailNodeConnector(),
472 new HashSet<Property>());
475 // Now remove the edge from edgesDB
476 this.edgesDB.remove(e);
478 // Now lets update the NodeConnectors DB, the assumption
479 // here is that two NodeConnector are exclusively
480 // connected by 1 and only 1 edge, this is reasonable in
481 // the same plug (virtual of phisical) we can assume two
482 // cables won't be plugged. This could break only in case
483 // of devices in the middle that acts as hubs, but it
484 // should be safe to assume that won't happen.
485 this.nodeConnectorsDB.remove(e.getHeadNodeConnector());
486 this.nodeConnectorsDB.remove(e.getTailNodeConnector());
489 Set<Property> old_props = this.edgesDB.get(e);
491 // When property changes lets make sure we can change it
492 // all except the creation time stamp because that should
493 // be changed only when the edge is destroyed and created
496 for (Property prop : old_props) {
497 if (prop instanceof TimeStamp) {
498 TimeStamp t = (TimeStamp) prop;
499 if (t.getTimeStampName().equals("creation")) {
505 // Now lest make sure new properties are non-null
506 // Make sure the props are non-null
508 props = (Set<Property>) new HashSet();
510 // Copy the set so noone is going to change the content
511 props = (Set<Property>) new HashSet(props);
514 // Now lets remove the creation property if exist in the
516 for (Iterator<Property> i = props.iterator(); i.hasNext();) {
517 Property prop = i.next();
518 if (prop instanceof TimeStamp) {
519 TimeStamp t = (TimeStamp) prop;
520 if (t.getTimeStampName().equals("creation")) {
526 // Now lets add the creation timestamp in it
532 this.edgesDB.put(e, props);
536 // Now update the listeners
537 for (ITopologyManagerAware s : this.topologyManagerAware) {
539 s.edgeUpdate(e, type, props);
540 } catch (Exception exc) {
541 log.error("Exception on callback", exc);
546 private Edge getReverseLinkTuple(TopologyUserLinkConfig link) {
547 TopologyUserLinkConfig rLink = new TopologyUserLinkConfig(
548 link.getName(), link.getDstNodeIDType(), link.getDstSwitchId(),
549 link.getDstNodeConnectorIDType(), link.getDstPort(),
550 link.getSrcNodeIDType(), link.getSrcSwitchId(),
551 link.getSrcNodeConnectorIDType(), link.getSrcPort());
552 return getLinkTuple(rLink);
555 private Edge getLinkTuple(TopologyUserLinkConfig link) {
556 Edge linkTuple = null;
558 // if atleast 1 link exists for the srcPort and atleast 1 link exists for the dstPort
559 // that makes it ineligible for the Manual link addition
560 // This is just an extra protection to avoid mis-programming.
561 boolean srcLinkExists = false;
562 boolean dstLinkExists = false;
563 //TODO check a way to validate the port with inventory services
564 //if (srcSw.getPorts().contains(srcPort) &&
565 //dstSw.getPorts().contains(srcPort) &&
566 if (!srcLinkExists && !dstLinkExists) {
569 NodeConnector sPort = null;
570 NodeConnector dPort = null;
572 String srcNodeIDType = link.getSrcNodeIDType();
573 String srcNodeConnectorIDType = link.getSrcNodeConnectorIDType();
574 String dstNodeIDType = link.getDstNodeIDType();
575 String dstNodeConnectorIDType = link.getDstNodeConnectorIDType();
577 if (srcNodeIDType.equals(NodeIDType.OPENFLOW)) {
578 sNode = new Node(srcNodeIDType, link.getSrcSwitchIDLong());
580 sNode = new Node(srcNodeIDType, link.getSrcSwitchId());
583 if (dstNodeIDType.equals(NodeIDType.OPENFLOW)) {
584 dNode = new Node(dstNodeIDType, link.getDstSwitchIDLong());
586 dNode = new Node(dstNodeIDType, link.getDstSwitchId());
589 if (srcNodeConnectorIDType.equals(NodeConnectorIDType.OPENFLOW)) {
590 Short srcPort = Short.valueOf((short) 0);
591 if (!link.isSrcPortByName()) {
592 srcPort = Short.parseShort(link.getSrcPort());
594 sPort = new NodeConnector(srcNodeConnectorIDType,
597 sPort = new NodeConnector(srcNodeConnectorIDType,
598 link.getSrcPort(), sNode);
601 if (dstNodeConnectorIDType.equals(NodeConnectorIDType.OPENFLOW)) {
602 Short dstPort = Short.valueOf((short) 0);
603 if (!link.isDstPortByName()) {
604 dstPort = Short.parseShort(link.getDstPort());
606 dPort = new NodeConnector(dstNodeConnectorIDType,
609 dPort = new NodeConnector(dstNodeConnectorIDType,
610 link.getDstPort(), dNode);
612 linkTuple = new Edge(sPort, dPort);
613 } catch (ConstructionException cex) {
614 log.warn("Caught exception ", cex);
619 if (srcLinkExists && dstLinkExists) {
620 link.setStatus(TopologyUserLinkConfig.STATUS.INCORRECT);
626 public ConcurrentMap<String, TopologyUserLinkConfig> getUserLinks() {
631 public Status addUserLink(TopologyUserLinkConfig link) {
632 if (!link.isValid()) {
633 return new Status(StatusCode.BADREQUEST,
634 "Configuration Invalid. Please check the parameters");
636 if (userLinks.get(link.getName()) != null) {
637 return new Status(StatusCode.CONFLICT,
638 "Link with name : " + link.getName()
639 + " already exists. Please use another name");
641 if (userLinks.containsValue(link)) {
642 return new Status(StatusCode.CONFLICT, "Link configuration exists");
645 link.setStatus(TopologyUserLinkConfig.STATUS.LINKDOWN);
646 userLinks.put(link.getName(), link);
648 Edge linkTuple = getLinkTuple(link);
649 if (linkTuple != null) {
651 linkTuple = getReverseLinkTuple(link);
652 link.setStatus(TopologyUserLinkConfig.STATUS.SUCCESS);
653 } catch (Exception e) {
654 return new Status(StatusCode.INTERNALERROR,
655 "Exception while adding custom link : " +
659 return new Status(StatusCode.SUCCESS, null);
663 public Status deleteUserLink(String linkName) {
664 if (linkName == null) {
665 return new Status(StatusCode.BADREQUEST,
666 "A valid linkName is required to Delete a link");
669 TopologyUserLinkConfig link = userLinks.get(linkName);
671 Edge linkTuple = getLinkTuple(link);
672 userLinks.remove(linkName);
673 if (linkTuple != null) {
675 //oneTopology.deleteUserConfiguredLink(linkTuple);
676 } catch (Exception e) {
678 .warn("Harmless : Exception while Deleting User Configured link "
679 + link + " " + e.toString());
681 linkTuple = getReverseLinkTuple(link);
683 //oneTopology.deleteUserConfiguredLink(linkTuple);
684 } catch (Exception e) {
686 .error("Harmless : Exception while Deleting User Configured Reverse link "
687 + link + " " + e.toString());
690 return new Status(StatusCode.SUCCESS, null);
693 private void registerWithOSGIConsole() {
694 BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass())
696 bundleContext.registerService(CommandProvider.class.getName(), this,
701 public String getHelp() {
702 StringBuffer help = new StringBuffer();
703 help.append("---Topology Manager---\n");
704 help.append("\t addTopo name <NodeIDType> <src-sw-id> <NodeConnectorIDType> <port-number> <NodeIDType> <dst-sw-id> <NodeConnectorIDType> <port-number>\n");
705 help.append("\t delTopo name\n");
706 help.append("\t printTopo\n");
707 help.append("\t printNodeEdges\n");
708 return help.toString();
711 public void _printTopo(CommandInterpreter ci) {
712 for (String name : this.userLinks.keySet()) {
713 TopologyUserLinkConfig linkConfig = userLinks.get(name);
714 ci.println("Name : " + name);
715 ci.println(linkConfig);
716 ci.println("Edge " + getLinkTuple(linkConfig));
717 ci.println("Reverse Edge " + getReverseLinkTuple(linkConfig));
721 public void _addTopo(CommandInterpreter ci) {
722 String name = ci.nextArgument();
723 if ((name == null)) {
724 ci.println("Please enter a valid Name");
728 String srcNodeIDType = ci.nextArgument();
729 if (srcNodeIDType == null) {
730 ci.println("Null source node ID Type. Example: OF or PR");
734 String dpid = ci.nextArgument();
736 ci.println("Null source node id");
740 String srcNodeConnectorIDType = ci.nextArgument();
741 if (srcNodeConnectorIDType == null) {
742 ci.println("Null source node connector ID Type. Example: OF or PR");
746 String port = ci.nextArgument();
748 ci.println("Null source port number");
752 String dstNodeIDType = ci.nextArgument();
753 if (dstNodeIDType == null) {
754 ci.println("Null destination node ID Type. Example: OF or PR");
758 String ddpid = ci.nextArgument();
760 ci.println("Null destination node ID");
764 String dstNodeConnectorIDType = ci.nextArgument();
765 if (dstNodeConnectorIDType == null) {
766 ci.println("Null destination node connector ID Type. Example: OF or PR");
770 String dport = ci.nextArgument();
772 ci.println("Null destination port number");
775 TopologyUserLinkConfig config = new TopologyUserLinkConfig(name,
776 srcNodeIDType, dpid, srcNodeConnectorIDType, port,
777 dstNodeIDType, ddpid, dstNodeConnectorIDType, dport);
778 ci.println(this.addUserLink(config));
781 public void _delTopo(CommandInterpreter ci) {
782 String name = ci.nextArgument();
783 if ((name == null)) {
784 ci.println("Please enter a valid Name");
787 this.deleteUserLink(name);
790 public void _printNodeEdges(CommandInterpreter ci) {
791 Map<Node, Set<Edge>> nodeEdges = getNodeEdges();
792 if (nodeEdges == null) {
795 Set<Node> nodeSet = nodeEdges.keySet();
796 if (nodeSet == null) {
799 ci.println(" Node Edge");
800 for (Node node : nodeSet) {
801 Set<Edge> edgeSet = nodeEdges.get(node);
802 if (edgeSet == null) {
805 for (Edge edge : edgeSet) {
806 ci.println(node + " " + edge);
812 public Object readObject(ObjectInputStream ois)
813 throws FileNotFoundException, IOException, ClassNotFoundException {
814 // TODO Auto-generated method stub
815 return ois.readObject();
819 public Status saveConfiguration() {
824 public void edgeOverUtilized(Edge edge) {
825 log.warn("Link Utilization above normal: " + edge);
829 public void edgeUtilBackToNormal(Edge edge) {
830 log.warn("Link Utilization back to normal: " + edge);