1   /****************************************************************************
2    * This demo file is part of yFiles for Java 2.14.
3    * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4    * 72070 Tuebingen, Germany. All rights reserved.
5    * 
6    * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7    * of demo files in source code or binary form, with or without
8    * modification, is not permitted.
9    * 
10   * Owners of a valid software license for a yFiles for Java version that this
11   * demo is shipped with are allowed to use the demo source code as basis
12   * for their own yFiles for Java powered applications. Use of such programs is
13   * governed by the rights and conditions as set out in the yFiles for Java
14   * license agreement.
15   * 
16   * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19   * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21   * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   *
27   ***************************************************************************/
28  package demo.view.networkmonitoring;
29  
30  import org.w3c.dom.Element;
31  import y.base.DataMap;
32  import y.base.Edge;
33  import y.base.EdgeCursor;
34  import y.base.EdgeList;
35  import y.base.Node;
36  import y.base.NodeCursor;
37  import y.base.NodeMap;
38  import y.base.YList;
39  import y.io.GraphMLIOHandler;
40  import y.io.graphml.KeyScope;
41  import y.io.graphml.KeyType;
42  import y.io.graphml.input.DeserializationEvent;
43  import y.io.graphml.input.DeserializationHandler;
44  import y.io.graphml.input.GraphMLParseException;
45  import y.util.DataProviderAdapter;
46  import y.util.GraphCopier;
47  import y.util.Maps;
48  import y.view.Graph2D;
49  
50  import javax.swing.Timer;
51  import java.awt.event.ActionEvent;
52  import java.awt.event.ActionListener;
53  import java.io.IOException;
54  import java.net.URL;
55  import java.util.Iterator;
56  import java.util.Random;
57  
58  /**
59   * This is an elementary network model that creates consistent data load. Data is sent between nodes without
60   * a specific target. Every element has a queue that stores sent data packets. Data load changes
61   * are turn-based.
62   * The nodes behave differently:
63   * <ul><li>Switches and W-LANs route data to the least busy node.</li>
64   * <li>Server and databases send (maybe more) data back to the node who send him data.</li>
65   * <li>PCs, Laptops and Tablets generate data and are the only target for data</li></ul>
66   */
67  public class NetworkModelImpl implements NetworkModel {
68  
69    /**
70     * The maximum number of cycles a data packet can stay at one network element until it gets deleted.
71     */
72    private static final int MAX_WAITING_CYCLES = 10;
73    /**
74     * The maximum number of data packets a PC generates in one cycle.
75     */
76    private static final int MAX_DATA_AMOUNT = 15;
77    /**
78     * Probability of data packet generation on a PC.
79     */
80    private static final double DATA_GENERATION_PROBABILITY = 0.02;
81    /**
82     * Map that holds the current state relative network load for each network element.
83     * 0-1 is data load, -1 is disabled, -2 is broken.
84     */
85    private final DataMap statusMap;
86    /**
87     * Map that holds the capacity for each network element.
88     */
89    private final DataMap volumeMap;
90    /**
91     * Map that holds a queue for each network element that contains all data packets currently located at this
92     * element.
93     */
94    private final DataMap loadMap;
95    private final NodeMap nodeKind;
96    private final NodeMap nodeInfo;
97    private boolean canFailure;
98    private final Random random = new Random(666);
99  
100   private final YList observerList;
101 
102   private final Graph2D graph;
103 
104   private final Timer timer;
105 
106 
107   public NetworkModelImpl(URL resource) {
108     observerList = new YList();
109     graph = new Graph2D();
110     //map a node to itself
111     graph.addDataProvider(ELEMENT_ID_DPKEY, new DataProviderAdapter() {
112       public Object get(Object dataHolder) {
113         return dataHolder;
114       }
115     });
116     statusMap = Maps.createHashedDataMap();
117     loadMap = Maps.createHashedDataMap();
118     nodeKind = Maps.createHashedNodeMap();
119     volumeMap = Maps.createHashedDataMap();
120     nodeInfo = Maps.createHashedNodeMap();
121 
122     graph.addDataProvider(NetworkModel.NODE_TYPE_DPKEY,nodeKind);
123     graph.addDataProvider(NetworkModel.ELEMENT_CAPACITY_DPKEY,volumeMap);
124     graph.addDataProvider(NetworkModel.NODE_INFO_DPKEY, nodeInfo);
125 
126     loadNetworkGraph(resource);
127     //calculate some steps to make the initial graph more interesting
128     for (int i = 0; i < 40; i++) {
129       updateState();
130     }
131     //This creates a timer that performs an action every 1500 milliseconds.
132     //The actions calculate the next network status and notifies all registered observer.
133     //This acts like a main loop for this model.
134     timer = new Timer(1500, new ActionListener() {
135       public void actionPerformed(ActionEvent e) {
136         updateState();
137         notifyObserver(statusMap);
138       }
139     });
140     timer.start();
141   }
142 
143   /**
144    * Load the network graph from a given file.
145    * @param resource <code>GraphML</code> file
146    */
147   private void loadNetworkGraph(URL resource) {
148     final GraphMLIOHandler ioh = new GraphMLIOHandler();
149     //register data acceptor to load additional data
150     ioh.getGraphMLHandler().addInputDataAcceptor("NetworkMonitoring.Volume",volumeMap,KeyScope.ALL,KeyType.INT);
151     ioh.getGraphMLHandler().addInputDataAcceptor("NetworkMonitoring.NodeKind", nodeKind, KeyScope.NODE, KeyType.INT);
152     ioh.getGraphMLHandler().addInputDataAcceptor("NetworkMonitoring.NodeInfo", nodeInfo, KeyScope.NODE,
153         new DeserializationHandler() {
154           public void onHandleDeserialization(DeserializationEvent event) throws GraphMLParseException {
155             final Element element = (Element) event.getXmlNode();
156             event.setResult(new NetworkNodeInfo(element.getAttribute("Name"), element.getAttribute("IP")));
157           }
158         });
159     try {
160       ioh.read(graph, resource);
161     } catch (IOException e1) {
162       e1.printStackTrace();
163     }
164     //initialize queues for all elements
165     for (final NodeCursor nodeCursor = graph.nodes();nodeCursor.ok();nodeCursor.next()) {
166       loadMap.set(nodeCursor.node(),new YList());
167     }
168     for (final EdgeCursor edges = graph.edges();edges.ok();edges.next()) {
169       loadMap.set(edges.edge(),new YList());
170     }
171   }
172 
173   /**
174    * Updates the next state of the network. Network nodes and connections pass their current date packet to the
175    * next destination.
176    */
177   private void updateState() {
178     //process data on edges and nodes
179     boolean hasCrashed = false;
180     for (final EdgeCursor edges = graph.edges(); edges.ok(); edges.next()) {
181       final Edge edge = edges.edge();
182       if (!hasCrashed) {
183         hasCrashed = tryCrash(edge);
184       }
185       if (isOK(edge)) {
186         updateEdge(edge);
187       }
188     }
189     for (final NodeCursor nodes = graph.nodes(); nodes.ok(); nodes.next()) {
190       final Node node = nodes.node();
191       if (!hasCrashed) {
192         hasCrashed = tryCrash(node);
193       }
194       if (isOK(node)) {
195         switch (nodeKind.getInt(node)) {
196           case PC:
197           case LAPTOP:
198           case SMARTPHONE:
199             updatePC(node);
200             break;
201           case SWITCH:
202           case WLAN:
203             updateSwitch(node);
204             break;
205           case SERVER:
206           case DATABASE:
207             updateServer(node);
208             break;
209         }
210       }
211     }
212     //calculate status values for view
213     for (final NodeCursor nodes = graph.nodes(); nodes.ok(); nodes.next()) {
214       final Node node = nodes.node();
215       if (isOK(node)) {
216         final double value = (double) ((YList) loadMap.get(node)).size() / (double) volumeMap.getInt(node);
217         statusMap.setDouble(node, value);
218       }
219     }
220     for (final EdgeCursor edges = graph.edges(); edges.ok(); edges.next()) {
221       final Edge edge = edges.edge();
222       if (isOK(edge)) {
223         final double value = (double) ((YList) loadMap.get(edge)).size() / (double) volumeMap.getInt(edge);
224         statusMap.setDouble(edge, value);
225       }
226     }
227   }
228 
229   /**
230    * Processes all data packets on a server.
231    * Servers send packets back to the last source of a data packet,
232    * randomly along with newly generated data packets.
233    * @param node the server node
234    */
235   private void updateServer(final Node node) {
236     final YList packetQueue = (YList) loadMap.get(node);
237     dropOldPackets(packetQueue);
238     final YList unsentPackets = new YList();
239     final int serverVolume = volumeMap.getInt(node);
240     for (final Iterator iterator = packetQueue.iterator(); iterator.hasNext(); ) {
241       DataPacket packet = (DataPacket) iterator.next();
242       //ignore packets that were send to the server in the same cycle
243       Edge edge = node.getEdge(packet.source);
244       if ((packet.getWaitingCycles() > 0) && isOK(edge)) {
245         final YList edgeQueue = (YList) loadMap.get(edge);
246         final int edgeVolume = volumeMap.getInt(edge);
247         //send back to source of data packet, if possible
248         if (edgeVolume > edgeQueue.size()) {
249           packet.setSource(node);
250           packet.resetWaitingCycles();
251           edgeQueue.add(packet);
252           iterator.remove();
253           //server response may contains  more packets than request
254           final int newPackages = random.nextInt(3);
255           for (int i = 0; i < newPackages; i++) {
256             if (edgeVolume > edgeQueue.size()) {
257               edgeQueue.add(new DataPacket(node));
258             } else if (serverVolume - unsentPackets.size() > packetQueue.size()) {
259               packet = new DataPacket(edge.opposite(node));
260               unsentPackets.add(packet);
261             }
262           }
263         } else {
264           packet.incrementWaitingCycles();
265         }
266       } else {
267         packet.incrementWaitingCycles();
268       }
269     }
270     //add unsent packets to queue
271     packetQueue.addAll(unsentPackets);
272   }
273 
274   /**
275    * Processes all data packets on a switch.
276    * Switches forward data packets to a connected node that was not the source node.
277    * The node whose connection has the most space left is chosen.
278    * @param node the switch node
279    */
280   private void updateSwitch(final Node node) {
281     final YList switchQueue = (YList) loadMap.get(node);
282     dropOldPackets(switchQueue);
283     //forward data packets, if possible
284     for (final Iterator iterator = switchQueue.iterator(); iterator.hasNext(); ) {
285       final DataPacket packet = (DataPacket) iterator.next();
286       //ignore packets that were send to the switch in the same cycle
287       if (packet.getWaitingCycles() > 0) {
288         final Edge edge = getBestEdge(node.edges(), packet.getSource());
289         if (edge != null) {
290           final YList edgeLoad = (YList) loadMap.get(edge);
291           packet.setSource(node);
292           packet.resetWaitingCycles();
293           edgeLoad.add(packet);
294           iterator.remove();
295         } else {
296           packet.incrementWaitingCycles();
297         }
298       } else {
299         packet.incrementWaitingCycles();
300       }
301     }
302 
303   }
304 
305   /**
306    * Determines the edge out of the given set of edges which has the most unused capacity.
307    * The edge that connects back to the source will be ignored.
308    * @param edges  available connections
309    * @param source source of the data packet
310    * @return edge with most available space, if there is any, otherwise null.
311    */
312   private Edge getBestEdge(final EdgeCursor edges, final Node source) {
313     EdgeList bestEdges = new EdgeList();
314     int bestSpace = 0;
315     for (; edges.ok(); edges.next()) {
316       Edge edge = edges.edge();
317       if (edge.target() != source && edge.source() != source && isOK(edge)) {
318         int edgeSpace = volumeMap.getInt(edge) - ((YList) loadMap.get(edge)).size();
319         if (edgeSpace > bestSpace) {
320           bestEdges = new EdgeList(edge);
321           bestSpace = edgeSpace;
322         } else if (edgeSpace > 0 && edgeSpace == bestSpace) {
323           bestEdges.add(edge);
324         }
325       }
326     }
327     if (bestEdges.isEmpty()) {
328       return null;
329     } else {
330       //if there are many best edges, choose one randomly
331       return (Edge) bestEdges.get(random.nextInt(bestEdges.size()));
332     }
333   }
334 
335   /**
336    * Processes data packets on a PC.
337    * Randomly generates new data. Data send to PC will be deleted.
338    * @param node the pc node
339    */
340   private void updatePC(final Node node) {
341     final YList load = (YList) loadMap.get(node);
342     dropOldPackets(load);
343     final int volume = volumeMap.getInt(node);
344     //as PC has one out edge
345     final Edge edge = node.edges().edge();
346     if (isOK(edge)) {
347       final YList edgeLoad = (YList) loadMap.get(edge);
348       int availableEdgeSpace = volumeMap.getInt(edge) - edgeLoad.size();
349       //first, process existing data packets
350       for (final Iterator iterator = load.iterator(); iterator.hasNext(); ) {
351         final DataPacket packet = (DataPacket) iterator.next();
352         if (packet.getWaitingCycles() > 0) {
353           if (packet.getSource() != node) {
354             iterator.remove();
355           } else if (availableEdgeSpace > 0) {
356             edgeLoad.add(packet);
357             packet.resetWaitingCycles();
358             availableEdgeSpace--;
359             iterator.remove();
360             //package stays at PC
361           } else {
362             packet.incrementWaitingCycles();
363           }
364         } else {
365           packet.incrementWaitingCycles();
366         }
367       }
368       //then, create new data packets
369       if (random.nextDouble() <= DATA_GENERATION_PROBABILITY) {
370         final int dataAmount = random.nextInt(MAX_DATA_AMOUNT);
371         for (int i = 0; i < dataAmount; i++) {
372           final DataPacket packet = new DataPacket(node);
373           if (availableEdgeSpace > 0) {
374             edgeLoad.add(packet);
375             availableEdgeSpace--;
376           } else if (volume > load.size()) {
377             load.add(packet);
378           } else {
379             //when queue of edge and PC are full, all following data will be dropped
380             break;
381           }
382         }
383       }
384     }
385   }
386 
387   /**
388    * Connections forward data packets to the node opposite of the packet source.
389    * @param edge the connection edge
390    */
391   private void updateEdge(final Edge edge) {
392     final YList edgeLoad = (YList) loadMap.get(edge);
393     dropOldPackets(edgeLoad);
394     final Node source = edge.source();
395     final Node target = edge.target();
396     final YList sourceLoad = (YList) loadMap.get(source);
397     final YList targetLoad = (YList) loadMap.get(target);
398     int availableSpaceSource = volumeMap.getInt(source) - sourceLoad.size();
399     int availableSpaceTarget = volumeMap.getInt(target) - targetLoad.size();
400     for (final Iterator iterator = edgeLoad.iterator(); iterator.hasNext(); ) {
401       DataPacket packet = (DataPacket) iterator.next();
402       if ((packet.getSource() == source) && (availableSpaceTarget > 0)) {
403         //packet came from source, so we try to send to target
404         targetLoad.add(packet);
405         packet.resetWaitingCycles();
406         availableSpaceTarget--;
407         iterator.remove();
408       } else if ((packet.getSource() == target) && (availableSpaceSource > 0)) {
409         //packet came from target, so we try to send to source
410         sourceLoad.add(packet);
411         packet.resetWaitingCycles();
412         availableSpaceSource--;
413         iterator.remove();
414       } else {
415         //packet stays at edge
416         packet.incrementWaitingCycles();
417       }
418     }
419   }
420 
421   /**
422    * Removes data from given list that stayed more than {@link #MAX_WAITING_CYCLES}.
423    * @param load queue
424    */
425   private void dropOldPackets(YList load) {
426     for (final Iterator iterator = load.iterator();iterator.hasNext();) {
427       final DataPacket next = (DataPacket) iterator.next();
428       if (next.getWaitingCycles() > MAX_WAITING_CYCLES) {
429         iterator.remove();
430       }
431     }
432   }
433 
434   /**
435    * Check by random if this element crashed.
436    * When the element is a node, crashing will deactivate all connected edges.
437    * @param id the element
438    * @return true if element crashed, false otherwise
439    */
440   private boolean tryCrash(Object id) {
441     if (canFailure && random.nextInt(3000) == 0) {
442       statusMap.setDouble(id, -2);
443       loadMap.set(id,new YList());
444       if (id instanceof Node) {
445         Node node = (Node) id;
446         for (final EdgeCursor edgeCursor = node.edges();edgeCursor.ok();edgeCursor.next()) {
447           disableConnection(edgeCursor.edge());
448         }
449       }
450       return true;
451     }
452     return false;
453   }
454 
455   /**
456    * Notifies observer of new step
457    */
458   private void notifyObserver(DataMap sendMap) {
459     final Iterator iterator = observerList.iterator();
460     while (iterator.hasNext()) {
461       final NetworkModelObserver observer = (NetworkModelObserver) iterator.next();
462       observer.update(sendMap);
463     }
464   }
465 
466   public void addObserver(final NetworkModelObserver observer) {
467     observerList.add(observer);
468   }
469 
470   public void removeObserver(final NetworkModelObserver observer) {
471     observerList.remove(observer);
472   }
473 
474   public Graph2D getNetworkModel() {
475     final GraphCopier graphCopier = new GraphCopier();
476     graphCopier.setCopyFactory(graph.getGraphCopyFactory());
477     graphCopier.setDataProviderContentCopying(true);
478     return (Graph2D) graphCopier.copy(graph);
479   }
480 
481   /**
482    * Check if an element is not broken or deactivated
483    * @param id the element
484    * @return true if element is not broken and not deactivated
485    */
486   public boolean isOK(final Object id) {
487     return statusMap.getDouble(id) >= 0;
488   }
489 
490   public void enableNetworkNode(final Object id) {
491     DataMap sendMap = Maps.createHashedDataMap();
492     statusMap.setDouble(id, 0);
493     final Node node = (Node) id;
494     for (final EdgeCursor edges = node.edges(); edges.ok(); edges.next()) {
495       final Edge edge = edges.edge();
496       if (isOK(edge.opposite(node)) && statusMap.getDouble(edge) != -2) {
497         enableConnection(edge);
498         statusMap.set(edge, new Double(0));
499         sendMap.set(edge,new Double(0));
500       }
501     }
502     sendMap.set(id,new Double(0));
503     notifyObserver(sendMap);
504   }
505 
506   public void disableNetworkNode(final Object id) {
507     DataMap sendMap = Maps.createHashedDataMap();
508     statusMap.setDouble(id, -1);
509     loadMap.set(id,new YList());
510     for (final EdgeCursor edges = ((Node) id).edges(); edges.ok(); edges.next()) {
511       final Edge edge = edges.edge();
512       if (isOK(edge)) {
513         sendMap.set(edge,new Double(-1));
514         statusMap.setDouble(edge, -1);
515         loadMap.set(edge, new YList());
516       }
517     }
518     sendMap.set(id,new Double(0));
519     notifyObserver(sendMap);
520   }
521 
522   public void enableConnection(final Object id) {
523     DataMap sendMap = Maps.createHashedDataMap();
524     sendMap.setDouble(id,0);
525     statusMap.set(id, new Double(0));
526     notifyObserver(sendMap);
527   }
528 
529   public void disableConnection(final Object id) {
530     if (isOK(id)) {
531       DataMap sendMap = Maps.createHashedDataMap();
532       statusMap.setDouble(id, -1);
533       sendMap.set(id, new Double(-1));
534       loadMap.set(id, new YList());
535       notifyObserver(sendMap);
536     }
537   }
538 
539   public void repairNetworkNode(final Object id) {
540     enableNetworkNode(id);
541   }
542 
543   public void repairEdge(final Object id) {
544     statusMap.setDouble(id, 0);
545     DataMap sendMap = Maps.createHashedDataMap();
546     sendMap.set(id,new Double(0));
547     notifyObserver(sendMap);
548   }
549 
550   public void setUpdateCycle(final int duration) {
551     timer.setDelay(duration);
552   }
553 
554   public int getUpdateCycle() {
555     return timer.getDelay();
556   }
557 
558   public void setNetworkErrorsEnabled(boolean errorsEnabled) {
559     this.canFailure = errorsEnabled;
560     if (!errorsEnabled) {
561       //if no failures should happen, repair all elements at once
562       for (final NodeCursor nodes = graph.nodes(); nodes.ok(); nodes.next()) {
563         final Node node = nodes.node();
564         if (statusMap.getDouble(node) == -2) {
565           repairNetworkNode(node);
566         }
567       }
568       for (final EdgeCursor edges = graph.edges(); edges.ok(); edges.next()) {
569         final Edge edge = edges.edge();
570         if (statusMap.getDouble(edge) == -2) {
571           repairEdge(edge);
572         }
573       }
574     }
575   }
576 
577   public boolean isNetworkErrorsEnabled() {
578     return canFailure;
579   }
580 
581   /**
582    * A DataPacket is the unit for data load.
583    */
584   private class DataPacket {
585     private Node source;
586     private int waitingCycles;
587 
588     private DataPacket(Node source) {
589       this.source = source;
590     }
591 
592     public Node getSource() {
593       return source;
594     }
595 
596     public void setSource(final Node source) {
597       this.source = source;
598     }
599 
600     public void incrementWaitingCycles() {
601       waitingCycles++;
602     }
603 
604     public void resetWaitingCycles() {
605       waitingCycles = 0;
606     }
607 
608     public int getWaitingCycles() {
609       return waitingCycles;
610     }
611   }
612 }
613