Ray Milkey
Committed by Gerrit Code Review

ONOS-3460 - Link provider that enforces strict configuration

This provider uses the "links" configuration of the network
config and only allows discovery of links that are in
the config.

Refactoring will be done in a subsequent patch set - the DiscoveryContext and
LinkDiscovery classes are duplicates, they need to be refactored so the
LLDP provider and the Network Config provider can share them.

Change-Id: I4de12fc1c4ffa05e3cac7767b8a31f48ba379f6c
...@@ -35,7 +35,7 @@ import static org.onosproject.net.DeviceId.deviceId; ...@@ -35,7 +35,7 @@ import static org.onosproject.net.DeviceId.deviceId;
35 description = "Lists all infrastructure links") 35 description = "Lists all infrastructure links")
36 public class LinksListCommand extends AbstractShellCommand { 36 public class LinksListCommand extends AbstractShellCommand {
37 37
38 - private static final String FMT = "src=%s/%s, dst=%s/%s, type=%s, state=%s%s"; 38 + private static final String FMT = "src=%s/%s, dst=%s/%s, type=%s, state=%s%s, expected=%s";
39 private static final String COMPACT = "%s/%s-%s/%s"; 39 private static final String COMPACT = "%s/%s-%s/%s";
40 40
41 @Argument(index = 0, name = "uri", description = "Device ID", 41 @Argument(index = 0, name = "uri", description = "Device ID",
...@@ -93,7 +93,8 @@ public class LinksListCommand extends AbstractShellCommand { ...@@ -93,7 +93,8 @@ public class LinksListCommand extends AbstractShellCommand {
93 return String.format(FMT, link.src().deviceId(), link.src().port(), 93 return String.format(FMT, link.src().deviceId(), link.src().port(),
94 link.dst().deviceId(), link.dst().port(), 94 link.dst().deviceId(), link.dst().port(),
95 link.type(), link.state(), 95 link.type(), link.state(),
96 - annotations(link.annotations())); 96 + annotations(link.annotations()),
97 + link.isExpected());
97 } 98 }
98 99
99 /** 100 /**
......
...@@ -119,7 +119,8 @@ public class DefaultLink extends AbstractProjectableModel implements Link { ...@@ -119,7 +119,8 @@ public class DefaultLink extends AbstractProjectableModel implements Link {
119 final DefaultLink other = (DefaultLink) obj; 119 final DefaultLink other = (DefaultLink) obj;
120 return Objects.equals(this.src, other.src) && 120 return Objects.equals(this.src, other.src) &&
121 Objects.equals(this.dst, other.dst) && 121 Objects.equals(this.dst, other.dst) &&
122 - Objects.equals(this.type, other.type); 122 + Objects.equals(this.type, other.type) &&
123 + Objects.equals(this.isExpected, other.isExpected);
123 } 124 }
124 return false; 125 return false;
125 } 126 }
......
...@@ -31,6 +31,10 @@ public class DefaultLinkDescription extends AbstractDescription ...@@ -31,6 +31,10 @@ public class DefaultLinkDescription extends AbstractDescription
31 private final ConnectPoint src; 31 private final ConnectPoint src;
32 private final ConnectPoint dst; 32 private final ConnectPoint dst;
33 private final Link.Type type; 33 private final Link.Type type;
34 + private final boolean isExpected;
35 +
36 + public static final boolean EXPECTED = true;
37 + public static final boolean NOT_EXPECTED = false;
34 38
35 /** 39 /**
36 * Creates a link description using the supplied information. 40 * Creates a link description using the supplied information.
...@@ -38,14 +42,32 @@ public class DefaultLinkDescription extends AbstractDescription ...@@ -38,14 +42,32 @@ public class DefaultLinkDescription extends AbstractDescription
38 * @param src link source 42 * @param src link source
39 * @param dst link destination 43 * @param dst link destination
40 * @param type link type 44 * @param type link type
45 + * @param isExpected is the link expected to be part of this configuration
41 * @param annotations optional key/value annotations 46 * @param annotations optional key/value annotations
42 */ 47 */
43 public DefaultLinkDescription(ConnectPoint src, ConnectPoint dst, 48 public DefaultLinkDescription(ConnectPoint src, ConnectPoint dst,
44 - Link.Type type, SparseAnnotations... annotations) { 49 + Link.Type type,
50 + boolean isExpected,
51 + SparseAnnotations... annotations) {
45 super(annotations); 52 super(annotations);
46 this.src = src; 53 this.src = src;
47 this.dst = dst; 54 this.dst = dst;
48 this.type = type; 55 this.type = type;
56 + this.isExpected = isExpected;
57 + }
58 +
59 + /**
60 + * Creates a link description using the supplied information.
61 + *
62 + * @param src link source
63 + * @param dst link destination
64 + * @param type link type
65 + * @param annotations optional key/value annotations
66 + */
67 + public DefaultLinkDescription(ConnectPoint src, ConnectPoint dst,
68 + Link.Type type,
69 + SparseAnnotations... annotations) {
70 + this(src, dst, type, EXPECTED, annotations);
49 } 71 }
50 72
51 @Override 73 @Override
...@@ -64,18 +86,24 @@ public class DefaultLinkDescription extends AbstractDescription ...@@ -64,18 +86,24 @@ public class DefaultLinkDescription extends AbstractDescription
64 } 86 }
65 87
66 @Override 88 @Override
89 + public boolean isExpected() {
90 + return isExpected;
91 + }
92 +
93 + @Override
67 public String toString() { 94 public String toString() {
68 return MoreObjects.toStringHelper(this) 95 return MoreObjects.toStringHelper(this)
69 .add("src", src()) 96 .add("src", src())
70 .add("dst", dst()) 97 .add("dst", dst())
71 .add("type", type()) 98 .add("type", type())
99 + .add("isExpected", isExpected())
72 .add("annotations", annotations()) 100 .add("annotations", annotations())
73 .toString(); 101 .toString();
74 } 102 }
75 103
76 @Override 104 @Override
77 public int hashCode() { 105 public int hashCode() {
78 - return Objects.hashCode(super.hashCode(), src, dst, type); 106 + return Objects.hashCode(super.hashCode(), src, dst, type, isExpected);
79 } 107 }
80 108
81 @Override 109 @Override
...@@ -87,7 +115,8 @@ public class DefaultLinkDescription extends AbstractDescription ...@@ -87,7 +115,8 @@ public class DefaultLinkDescription extends AbstractDescription
87 DefaultLinkDescription that = (DefaultLinkDescription) object; 115 DefaultLinkDescription that = (DefaultLinkDescription) object;
88 return Objects.equal(this.src, that.src) 116 return Objects.equal(this.src, that.src)
89 && Objects.equal(this.dst, that.dst) 117 && Objects.equal(this.dst, that.dst)
90 - && Objects.equal(this.type, that.type); 118 + && Objects.equal(this.type, that.type)
119 + && Objects.equal(this.isExpected, that.isExpected);
91 } 120 }
92 return false; 121 return false;
93 } 122 }
......
...@@ -45,5 +45,12 @@ public interface LinkDescription extends Description { ...@@ -45,5 +45,12 @@ public interface LinkDescription extends Description {
45 */ 45 */
46 Link.Type type(); 46 Link.Type type();
47 47
48 + /**
49 + * Returns true if the link is expected, false otherwise.
50 + *
51 + * @return expected flag
52 + */
53 + boolean isExpected();
54 +
48 // Add further link attributes 55 // Add further link attributes
49 } 56 }
......
1 +/*
2 + * Copyright 2016 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.store.link.impl;
17 +
18 +import org.onosproject.core.ApplicationId;
19 +import org.onosproject.net.config.Config;
20 +
21 +public class CoreConfig extends Config<ApplicationId> {
22 +
23 + private static final String LINK_DISCOVERY_MODE = "linkDiscoveryMode";
24 +
25 + protected static final String DEFAULT_LINK_DISCOVERY_MODE = "PERMISSIVE";
26 +
27 + /**
28 + * Returns the link discovery mode.
29 + *
30 + * @return link discovery mode
31 + */
32 + public ECLinkStore.LinkDiscoveryMode linkDiscoveryMode() {
33 + return ECLinkStore.LinkDiscoveryMode
34 + .valueOf(get(LINK_DISCOVERY_MODE, DEFAULT_LINK_DISCOVERY_MODE));
35 + }
36 +}
37 +
...@@ -15,21 +15,6 @@ ...@@ -15,21 +15,6 @@
15 */ 15 */
16 package org.onosproject.store.link.impl; 16 package org.onosproject.store.link.impl;
17 17
18 -import static com.google.common.base.Preconditions.checkNotNull;
19 -import static org.onosproject.net.DefaultAnnotations.merge;
20 -import static org.onosproject.net.DefaultAnnotations.union;
21 -import static org.onosproject.net.Link.State.ACTIVE;
22 -import static org.onosproject.net.Link.State.INACTIVE;
23 -import static org.onosproject.net.Link.Type.DIRECT;
24 -import static org.onosproject.net.Link.Type.INDIRECT;
25 -import static org.onosproject.net.LinkKey.linkKey;
26 -import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
27 -import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
28 -import static org.onosproject.net.link.LinkEvent.Type.LINK_UPDATED;
29 -import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.PUT;
30 -import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.REMOVE;
31 -import static org.slf4j.LoggerFactory.getLogger;
32 -
33 import java.util.Collection; 18 import java.util.Collection;
34 import java.util.Map; 19 import java.util.Map;
35 import java.util.Objects; 20 import java.util.Objects;
...@@ -48,6 +33,8 @@ import org.onlab.util.KryoNamespace; ...@@ -48,6 +33,8 @@ import org.onlab.util.KryoNamespace;
48 import org.onlab.util.SharedExecutors; 33 import org.onlab.util.SharedExecutors;
49 import org.onosproject.cluster.ClusterService; 34 import org.onosproject.cluster.ClusterService;
50 import org.onosproject.cluster.NodeId; 35 import org.onosproject.cluster.NodeId;
36 +import org.onosproject.core.ApplicationId;
37 +import org.onosproject.core.CoreService;
51 import org.onosproject.mastership.MastershipService; 38 import org.onosproject.mastership.MastershipService;
52 import org.onosproject.net.AnnotationKeys; 39 import org.onosproject.net.AnnotationKeys;
53 import org.onosproject.net.AnnotationsUtil; 40 import org.onosproject.net.AnnotationsUtil;
...@@ -56,8 +43,12 @@ import org.onosproject.net.DefaultAnnotations; ...@@ -56,8 +43,12 @@ import org.onosproject.net.DefaultAnnotations;
56 import org.onosproject.net.DefaultLink; 43 import org.onosproject.net.DefaultLink;
57 import org.onosproject.net.DeviceId; 44 import org.onosproject.net.DeviceId;
58 import org.onosproject.net.Link; 45 import org.onosproject.net.Link;
59 -import org.onosproject.net.LinkKey;
60 import org.onosproject.net.Link.Type; 46 import org.onosproject.net.Link.Type;
47 +import org.onosproject.net.LinkKey;
48 +import org.onosproject.net.config.ConfigFactory;
49 +import org.onosproject.net.config.NetworkConfigEvent;
50 +import org.onosproject.net.config.NetworkConfigListener;
51 +import org.onosproject.net.config.NetworkConfigRegistry;
61 import org.onosproject.net.device.DeviceClockService; 52 import org.onosproject.net.device.DeviceClockService;
62 import org.onosproject.net.link.DefaultLinkDescription; 53 import org.onosproject.net.link.DefaultLinkDescription;
63 import org.onosproject.net.link.LinkDescription; 54 import org.onosproject.net.link.LinkDescription;
...@@ -82,6 +73,22 @@ import com.google.common.collect.Iterables; ...@@ -82,6 +73,22 @@ import com.google.common.collect.Iterables;
82 import com.google.common.collect.Maps; 73 import com.google.common.collect.Maps;
83 import com.google.common.util.concurrent.Futures; 74 import com.google.common.util.concurrent.Futures;
84 75
76 +import static com.google.common.base.Preconditions.checkNotNull;
77 +import static org.onosproject.net.DefaultAnnotations.merge;
78 +import static org.onosproject.net.DefaultAnnotations.union;
79 +import static org.onosproject.net.Link.State.ACTIVE;
80 +import static org.onosproject.net.Link.State.INACTIVE;
81 +import static org.onosproject.net.Link.Type.DIRECT;
82 +import static org.onosproject.net.Link.Type.INDIRECT;
83 +import static org.onosproject.net.LinkKey.linkKey;
84 +import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
85 +import static org.onosproject.net.link.LinkEvent.Type.LINK_ADDED;
86 +import static org.onosproject.net.link.LinkEvent.Type.LINK_REMOVED;
87 +import static org.onosproject.net.link.LinkEvent.Type.LINK_UPDATED;
88 +import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.PUT;
89 +import static org.onosproject.store.service.EventuallyConsistentMapEvent.Type.REMOVE;
90 +import static org.slf4j.LoggerFactory.getLogger;
91 +
85 /** 92 /**
86 * Manages the inventory of links using a {@code EventuallyConsistentMap}. 93 * Manages the inventory of links using a {@code EventuallyConsistentMap}.
87 */ 94 */
...@@ -91,11 +98,29 @@ public class ECLinkStore ...@@ -91,11 +98,29 @@ public class ECLinkStore
91 extends AbstractStore<LinkEvent, LinkStoreDelegate> 98 extends AbstractStore<LinkEvent, LinkStoreDelegate>
92 implements LinkStore { 99 implements LinkStore {
93 100
101 + /**
102 + * Modes for dealing with newly discovered links.
103 + */
104 + protected enum LinkDiscoveryMode {
105 + /**
106 + * Permissive mode - all newly discovered links are valid.
107 + */
108 + PERMISSIVE,
109 +
110 + /**
111 + * Strict mode - all newly discovered links must be defined in
112 + * the network config.
113 + */
114 + STRICT
115 + }
116 +
94 private final Logger log = getLogger(getClass()); 117 private final Logger log = getLogger(getClass());
95 118
96 private final Map<LinkKey, Link> links = Maps.newConcurrentMap(); 119 private final Map<LinkKey, Link> links = Maps.newConcurrentMap();
97 private EventuallyConsistentMap<Provided<LinkKey>, LinkDescription> linkDescriptions; 120 private EventuallyConsistentMap<Provided<LinkKey>, LinkDescription> linkDescriptions;
98 121
122 + private ApplicationId appId;
123 +
99 private static final MessageSubject LINK_INJECT_MESSAGE = new MessageSubject("inject-link-request"); 124 private static final MessageSubject LINK_INJECT_MESSAGE = new MessageSubject("inject-link-request");
100 125
101 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 126 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
...@@ -113,9 +138,20 @@ public class ECLinkStore ...@@ -113,9 +138,20 @@ public class ECLinkStore
113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 138 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114 protected ClusterService clusterService; 139 protected ClusterService clusterService;
115 140
141 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
142 + protected NetworkConfigRegistry netCfgService;
143 +
144 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
145 + protected CoreService coreService;
146 +
116 private EventuallyConsistentMapListener<Provided<LinkKey>, LinkDescription> linkTracker = 147 private EventuallyConsistentMapListener<Provided<LinkKey>, LinkDescription> linkTracker =
117 new InternalLinkTracker(); 148 new InternalLinkTracker();
118 149
150 + // Listener for config changes
151 + private final InternalConfigListener cfgListener = new InternalConfigListener();
152 +
153 + protected LinkDiscoveryMode linkDiscoveryMode = LinkDiscoveryMode.STRICT;
154 +
119 protected static final KryoSerializer SERIALIZER = new KryoSerializer() { 155 protected static final KryoSerializer SERIALIZER = new KryoSerializer() {
120 @Override 156 @Override
121 protected void setupKryoPool() { 157 protected void setupKryoPool() {
...@@ -129,6 +165,12 @@ public class ECLinkStore ...@@ -129,6 +165,12 @@ public class ECLinkStore
129 165
130 @Activate 166 @Activate
131 public void activate() { 167 public void activate() {
168 + appId = coreService.registerApplication("org.onosproject.core");
169 + netCfgService.registerConfigFactory(factory);
170 + netCfgService.addListener(cfgListener);
171 +
172 + cfgListener.reconfigure(netCfgService.getConfig(appId, CoreConfig.class));
173 +
132 KryoNamespace.Builder serializer = KryoNamespace.newBuilder() 174 KryoNamespace.Builder serializer = KryoNamespace.newBuilder()
133 .register(KryoNamespaces.API) 175 .register(KryoNamespaces.API)
134 .register(MastershipBasedTimestamp.class) 176 .register(MastershipBasedTimestamp.class)
...@@ -162,6 +204,8 @@ public class ECLinkStore ...@@ -162,6 +204,8 @@ public class ECLinkStore
162 linkDescriptions.destroy(); 204 linkDescriptions.destroy();
163 links.clear(); 205 links.clear();
164 clusterCommunicator.removeSubscriber(LINK_INJECT_MESSAGE); 206 clusterCommunicator.removeSubscriber(LINK_INJECT_MESSAGE);
207 + netCfgService.removeListener(cfgListener);
208 + netCfgService.unregisterConfigFactory(factory);
165 209
166 log.info("Stopped"); 210 log.info("Stopped");
167 } 211 }
...@@ -255,6 +299,7 @@ public class ECLinkStore ...@@ -255,6 +299,7 @@ public class ECLinkStore
255 current.src(), 299 current.src(),
256 current.dst(), 300 current.dst(),
257 current.type() == DIRECT ? DIRECT : updated.type(), 301 current.type() == DIRECT ? DIRECT : updated.type(),
302 + current.isExpected(),
258 union(current.annotations(), updated.annotations())); 303 union(current.annotations(), updated.annotations()));
259 } 304 }
260 return updated; 305 return updated;
...@@ -268,6 +313,7 @@ public class ECLinkStore ...@@ -268,6 +313,7 @@ public class ECLinkStore
268 eventType.set(LINK_ADDED); 313 eventType.set(LINK_ADDED);
269 return newLink; 314 return newLink;
270 } else if (existingLink.state() != newLink.state() || 315 } else if (existingLink.state() != newLink.state() ||
316 + existingLink.isExpected() != newLink.isExpected() ||
271 (existingLink.type() == INDIRECT && newLink.type() == DIRECT) || 317 (existingLink.type() == INDIRECT && newLink.type() == DIRECT) ||
272 !AnnotationsUtil.isEqual(existingLink.annotations(), newLink.annotations())) { 318 !AnnotationsUtil.isEqual(existingLink.annotations(), newLink.annotations())) {
273 eventType.set(LINK_UPDATED); 319 eventType.set(LINK_UPDATED);
...@@ -316,14 +362,28 @@ public class ECLinkStore ...@@ -316,14 +362,28 @@ public class ECLinkStore
316 linkDescriptions.get(key).annotations())); 362 linkDescriptions.get(key).annotations()));
317 }); 363 });
318 364
319 - boolean isDurable = Objects.equals(annotations.get().value(AnnotationKeys.DURABLE), "true"); 365 + Link.State initialLinkState;
366 +
367 + boolean isExpected;
368 + if (linkDiscoveryMode == LinkDiscoveryMode.PERMISSIVE) {
369 + initialLinkState = ACTIVE;
370 + isExpected =
371 + Objects.equals(annotations.get().value(AnnotationKeys.DURABLE), "true");
372 + } else {
373 + initialLinkState = base.isExpected() ? ACTIVE : INACTIVE;
374 + isExpected = base.isExpected();
375 + }
376 +
377 +
378 +
379 +
320 return DefaultLink.builder() 380 return DefaultLink.builder()
321 .providerId(baseProviderId) 381 .providerId(baseProviderId)
322 .src(src) 382 .src(src)
323 .dst(dst) 383 .dst(dst)
324 .type(type) 384 .type(type)
325 - .state(ACTIVE) 385 + .state(initialLinkState)
326 - .isExpected(isDurable) 386 + .isExpected(isExpected)
327 .annotations(annotations.get()) 387 .annotations(annotations.get())
328 .build(); 388 .build();
329 } 389 }
...@@ -350,7 +410,7 @@ public class ECLinkStore ...@@ -350,7 +410,7 @@ public class ECLinkStore
350 return null; 410 return null;
351 } 411 }
352 412
353 - if (link.isDurable()) { 413 + if (linkDiscoveryMode == LinkDiscoveryMode.PERMISSIVE && link.isExpected()) {
354 // FIXME: this will not sync link state!!! 414 // FIXME: this will not sync link state!!!
355 return link.state() == INACTIVE ? null : 415 return link.state() == INACTIVE ? null :
356 updateLink(linkKey(link.src(), link.dst()), link, 416 updateLink(linkKey(link.src(), link.dst()), link,
...@@ -421,4 +481,47 @@ public class ECLinkStore ...@@ -421,4 +481,47 @@ public class ECLinkStore
421 } 481 }
422 } 482 }
423 } 483 }
484 +
485 + private class InternalConfigListener implements NetworkConfigListener {
486 +
487 + void reconfigure(CoreConfig coreConfig) {
488 + if (coreConfig == null) {
489 + linkDiscoveryMode = LinkDiscoveryMode.PERMISSIVE;
490 + } else {
491 + linkDiscoveryMode = coreConfig.linkDiscoveryMode();
492 + }
493 + if (linkDiscoveryMode == LinkDiscoveryMode.STRICT) {
494 + // Remove any previous links to force them to go through the strict
495 + // discovery process
496 + linkDescriptions.clear();
497 + links.clear();
498 + }
499 + log.debug("config set link discovery mode to {}",
500 + linkDiscoveryMode.name());
501 + }
502 +
503 + @Override
504 + public void event(NetworkConfigEvent event) {
505 +
506 + if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
507 + event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
508 + event.configClass().equals(CoreConfig.class)) {
509 +
510 + CoreConfig cfg = netCfgService.getConfig(appId, CoreConfig.class);
511 + reconfigure(cfg);
512 + log.info("Reconfigured");
513 + }
514 + }
515 + }
516 +
517 + // Configuration properties factory
518 + private final ConfigFactory factory =
519 + new ConfigFactory<ApplicationId, CoreConfig>(APP_SUBJECT_FACTORY,
520 + CoreConfig.class,
521 + "core") {
522 + @Override
523 + public CoreConfig createConfig() {
524 + return new CoreConfig();
525 + }
526 + };
424 } 527 }
......
...@@ -15,12 +15,20 @@ ...@@ -15,12 +15,20 @@
15 */ 15 */
16 package org.onosproject.store.link.impl; 16 package org.onosproject.store.link.impl;
17 17
18 -import com.google.common.base.Function; 18 +import java.io.IOException;
19 -import com.google.common.collect.FluentIterable; 19 +import java.util.Collections;
20 -import com.google.common.collect.ImmutableList; 20 +import java.util.HashMap;
21 -import com.google.common.collect.Multimaps; 21 +import java.util.HashSet;
22 -import com.google.common.collect.SetMultimap; 22 +import java.util.Map;
23 -import com.google.common.collect.Sets; 23 +import java.util.Map.Entry;
24 +import java.util.Set;
25 +import java.util.concurrent.ConcurrentHashMap;
26 +import java.util.concurrent.ConcurrentMap;
27 +import java.util.concurrent.ExecutorService;
28 +import java.util.concurrent.Executors;
29 +import java.util.concurrent.ScheduledExecutorService;
30 +import java.util.concurrent.TimeUnit;
31 +
24 import org.apache.commons.lang3.RandomUtils; 32 import org.apache.commons.lang3.RandomUtils;
25 import org.apache.felix.scr.annotations.Activate; 33 import org.apache.felix.scr.annotations.Activate;
26 import org.apache.felix.scr.annotations.Deactivate; 34 import org.apache.felix.scr.annotations.Deactivate;
...@@ -32,7 +40,6 @@ import org.onosproject.cluster.ClusterService; ...@@ -32,7 +40,6 @@ import org.onosproject.cluster.ClusterService;
32 import org.onosproject.cluster.ControllerNode; 40 import org.onosproject.cluster.ControllerNode;
33 import org.onosproject.cluster.NodeId; 41 import org.onosproject.cluster.NodeId;
34 import org.onosproject.mastership.MastershipService; 42 import org.onosproject.mastership.MastershipService;
35 -import org.onosproject.net.AnnotationKeys;
36 import org.onosproject.net.AnnotationsUtil; 43 import org.onosproject.net.AnnotationsUtil;
37 import org.onosproject.net.ConnectPoint; 44 import org.onosproject.net.ConnectPoint;
38 import org.onosproject.net.DefaultAnnotations; 45 import org.onosproject.net.DefaultAnnotations;
...@@ -60,20 +67,12 @@ import org.onosproject.store.serializers.KryoSerializer; ...@@ -60,20 +67,12 @@ import org.onosproject.store.serializers.KryoSerializer;
60 import org.onosproject.store.serializers.custom.DistributedStoreSerializers; 67 import org.onosproject.store.serializers.custom.DistributedStoreSerializers;
61 import org.slf4j.Logger; 68 import org.slf4j.Logger;
62 69
63 -import java.io.IOException; 70 +import com.google.common.base.Function;
64 -import java.util.Collections; 71 +import com.google.common.collect.FluentIterable;
65 -import java.util.HashMap; 72 +import com.google.common.collect.ImmutableList;
66 -import java.util.HashSet; 73 +import com.google.common.collect.Multimaps;
67 -import java.util.Map; 74 +import com.google.common.collect.SetMultimap;
68 -import java.util.Map.Entry; 75 +import com.google.common.collect.Sets;
69 -import java.util.Objects;
70 -import java.util.Set;
71 -import java.util.concurrent.ConcurrentHashMap;
72 -import java.util.concurrent.ConcurrentMap;
73 -import java.util.concurrent.ExecutorService;
74 -import java.util.concurrent.Executors;
75 -import java.util.concurrent.ScheduledExecutorService;
76 -import java.util.concurrent.TimeUnit;
77 76
78 import static com.google.common.base.Preconditions.checkNotNull; 77 import static com.google.common.base.Preconditions.checkNotNull;
79 import static com.google.common.base.Predicates.notNull; 78 import static com.google.common.base.Predicates.notNull;
...@@ -433,7 +432,9 @@ public class GossipLinkStore ...@@ -433,7 +432,9 @@ public class GossipLinkStore
433 new DefaultLinkDescription( 432 new DefaultLinkDescription(
434 linkDescription.value().src(), 433 linkDescription.value().src(),
435 linkDescription.value().dst(), 434 linkDescription.value().dst(),
436 - newType, merged), 435 + newType,
436 + existingLinkDescription.value().isExpected(),
437 + merged),
437 linkDescription.timestamp()); 438 linkDescription.timestamp());
438 } 439 }
439 return descs.put(providerId, newLinkDescription); 440 return descs.put(providerId, newLinkDescription);
...@@ -608,14 +609,17 @@ public class GossipLinkStore ...@@ -608,14 +609,17 @@ public class GossipLinkStore
608 annotations = merge(annotations, e.getValue().value().annotations()); 609 annotations = merge(annotations, e.getValue().value().annotations());
609 } 610 }
610 611
611 - boolean isDurable = Objects.equals(annotations.value(AnnotationKeys.DURABLE), "true"); 612 + //boolean isDurable = Objects.equals(annotations.value(AnnotationKeys.DURABLE), "true");
613 +
614 + // TEMP
615 + Link.State initialLinkState = base.value().isExpected() ? ACTIVE : INACTIVE;
612 return DefaultLink.builder() 616 return DefaultLink.builder()
613 .providerId(baseProviderId) 617 .providerId(baseProviderId)
614 .src(src) 618 .src(src)
615 .dst(dst) 619 .dst(dst)
616 .type(type) 620 .type(type)
617 - .state(ACTIVE) 621 + .state(initialLinkState)
618 - .isExpected(isDurable) 622 + .isExpected(base.value().isExpected())
619 .annotations(annotations) 623 .annotations(annotations)
620 .build(); 624 .build();
621 } 625 }
......
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2014 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<project xmlns="http://maven.apache.org/POM/4.0.0"
18 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
20 + <parent>
21 + <artifactId>onos-providers</artifactId>
22 + <groupId>org.onosproject</groupId>
23 + <version>1.5.0-SNAPSHOT</version>
24 + </parent>
25 + <modelVersion>4.0.0</modelVersion>
26 +
27 + <artifactId>onos-netcfg-links-provider</artifactId>
28 + <packaging>bundle</packaging>
29 +
30 + <description>
31 + Links provider that uses network config service to predefine devices and links.
32 + </description>
33 + <url>http://onosproject.org</url>
34 +
35 + <properties>
36 + <onos.app.name>org.onosproject.netcfglinksprovider</onos.app.name>
37 + <onos.app.origin>ON.Lab</onos.app.origin>
38 + </properties>
39 +
40 + <dependencies>
41 +
42 + <dependency>
43 + <groupId>org.onosproject</groupId>
44 + <artifactId>onlab-osgi</artifactId>
45 + </dependency>
46 +
47 + <dependency>
48 + <groupId>junit</groupId>
49 + <artifactId>junit</artifactId>
50 + <version>4.11</version>
51 + <scope>test</scope>
52 + </dependency>
53 +
54 + <dependency>
55 + <groupId>org.easymock</groupId>
56 + <artifactId>easymock</artifactId>
57 + <scope>test</scope>
58 + </dependency>
59 + </dependencies>
60 +
61 +</project>
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.provider.netcfglinks;
17 +
18 +import org.onosproject.mastership.MastershipService;
19 +import org.onosproject.net.LinkKey;
20 +import org.onosproject.net.link.LinkProviderService;
21 +import org.onosproject.net.packet.PacketService;
22 +
23 +/**
24 + * Shared context for use by link discovery.
25 + */
26 +interface DiscoveryContext {
27 +
28 + /**
29 + * Returns the shared mastership service reference.
30 + *
31 + * @return mastership service
32 + */
33 + MastershipService mastershipService();
34 +
35 + /**
36 + * Returns the shared link provider service reference.
37 + *
38 + * @return link provider service
39 + */
40 + LinkProviderService providerService();
41 +
42 + /**
43 + * Returns the shared packet service reference.
44 + *
45 + * @return packet service
46 + */
47 + PacketService packetService();
48 +
49 + /**
50 + * Returns the probe rate in millis.
51 + *
52 + * @return probe rate
53 + */
54 + long probeRate();
55 +
56 + /**
57 + * Indicates whether to emit BDDP.
58 + *
59 + * @return true to emit BDDP
60 + */
61 + boolean useBddp();
62 +
63 + /**
64 + * Touches the link identified by the given key to indicate that it's active.
65 + *
66 + * @param key link key
67 + */
68 + void touchLink(LinkKey key);
69 +}
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.provider.netcfglinks;
17 +
18 +import java.nio.ByteBuffer;
19 +import java.util.Set;
20 +
21 +import org.jboss.netty.util.Timeout;
22 +import org.jboss.netty.util.TimerTask;
23 +import org.onlab.packet.Ethernet;
24 +import org.onlab.packet.ONOSLLDP;
25 +import org.onlab.util.Timer;
26 +import org.onosproject.net.ConnectPoint;
27 +import org.onosproject.net.Device;
28 +import org.onosproject.net.DeviceId;
29 +import org.onosproject.net.Link.Type;
30 +import org.onosproject.net.LinkKey;
31 +import org.onosproject.net.Port;
32 +import org.onosproject.net.PortNumber;
33 +import org.onosproject.net.link.DefaultLinkDescription;
34 +import org.onosproject.net.link.LinkDescription;
35 +import org.onosproject.net.packet.DefaultOutboundPacket;
36 +import org.onosproject.net.packet.OutboundPacket;
37 +import org.onosproject.net.packet.PacketContext;
38 +import org.slf4j.Logger;
39 +
40 +import com.google.common.collect.Sets;
41 +
42 +import static java.util.concurrent.TimeUnit.MILLISECONDS;
43 +import static org.onosproject.net.PortNumber.portNumber;
44 +import static org.onosproject.net.flow.DefaultTrafficTreatment.builder;
45 +import static org.slf4j.LoggerFactory.getLogger;
46 +
47 +/**
48 + * Run discovery process from a physical switch. Ports are initially labeled as
49 + * slow ports. When an LLDP is successfully received, label the remote port as
50 + * fast. Every probeRate milliseconds, loop over all fast ports and send an
51 + * LLDP, send an LLDP for a single slow port. Based on FlowVisor topology
52 + * discovery implementation.
53 + */
54 +class LinkDiscovery implements TimerTask {
55 +
56 + private final Logger log = getLogger(getClass());
57 +
58 + private static final String SRC_MAC = "DE:AD:BE:EF:BA:11";
59 +
60 + private final Device device;
61 + private final DiscoveryContext context;
62 +
63 + private final ONOSLLDP lldpPacket;
64 + private final Ethernet ethPacket;
65 + private final Ethernet bddpEth;
66 +
67 + private Timeout timeout;
68 + private volatile boolean isStopped;
69 +
70 + // Set of ports to be probed
71 + private final Set<Long> ports = Sets.newConcurrentHashSet();
72 +
73 + /**
74 + * Instantiates discovery manager for the given physical switch. Creates a
75 + * generic LLDP packet that will be customized for the port it is sent out on.
76 + * Starts the the timer for the discovery process.
77 + *
78 + * @param device the physical switch
79 + * @param context discovery context
80 + */
81 + LinkDiscovery(Device device, DiscoveryContext context) {
82 + this.device = device;
83 + this.context = context;
84 +
85 + lldpPacket = new ONOSLLDP();
86 + lldpPacket.setChassisId(device.chassisId());
87 + lldpPacket.setDevice(device.id().toString());
88 +
89 + ethPacket = new Ethernet();
90 + ethPacket.setEtherType(Ethernet.TYPE_LLDP);
91 + ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA);
92 + ethPacket.setPayload(this.lldpPacket);
93 + ethPacket.setPad(true);
94 +
95 + bddpEth = new Ethernet();
96 + bddpEth.setPayload(lldpPacket);
97 + bddpEth.setEtherType(Ethernet.TYPE_BSN);
98 + bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST);
99 + bddpEth.setPad(true);
100 +
101 + isStopped = true;
102 + start();
103 + log.debug("Started discovery manager for switch {}", device.id());
104 +
105 + }
106 +
107 + synchronized void stop() {
108 + if (!isStopped) {
109 + isStopped = true;
110 + timeout.cancel();
111 + } else {
112 + log.warn("LinkDiscovery stopped multiple times?");
113 + }
114 + }
115 +
116 + synchronized void start() {
117 + if (isStopped) {
118 + isStopped = false;
119 + timeout = Timer.getTimer().newTimeout(this, 0, MILLISECONDS);
120 + } else {
121 + log.warn("LinkDiscovery started multiple times?");
122 + }
123 + }
124 +
125 + synchronized boolean isStopped() {
126 + return isStopped || timeout.isCancelled();
127 + }
128 +
129 + /**
130 + * Add physical port to discovery process.
131 + * Send out initial LLDP and label it as slow port.
132 + *
133 + * @param port the port
134 + */
135 + void addPort(Port port) {
136 + boolean newPort = ports.add(port.number().toLong());
137 + boolean isMaster = context.mastershipService().isLocalMaster(device.id());
138 + if (newPort && isMaster) {
139 + log.debug("Sending initial probe to port {}@{}", port.number().toLong(), device.id());
140 + sendProbes(port.number().toLong());
141 + }
142 + }
143 +
144 + /**
145 + * removed physical port from discovery process.
146 + * @param port the port number
147 + */
148 + void removePort(PortNumber port) {
149 + ports.remove(port.toLong());
150 + }
151 +
152 + /**
153 + * Handles an incoming LLDP packet. Creates link in topology and adds the
154 + * link for staleness tracking.
155 + *
156 + * @param packetContext packet context
157 + * @return true if handled
158 + */
159 + boolean handleLldp(PacketContext packetContext) {
160 + Ethernet eth = packetContext.inPacket().parsed();
161 + if (eth == null) {
162 + return false;
163 + }
164 +
165 + ONOSLLDP onoslldp = ONOSLLDP.parseONOSLLDP(eth);
166 + if (onoslldp != null) {
167 + PortNumber srcPort = portNumber(onoslldp.getPort());
168 + PortNumber dstPort = packetContext.inPacket().receivedFrom().port();
169 + DeviceId srcDeviceId = DeviceId.deviceId(onoslldp.getDeviceString());
170 + DeviceId dstDeviceId = packetContext.inPacket().receivedFrom().deviceId();
171 +
172 + ConnectPoint src = new ConnectPoint(srcDeviceId, srcPort);
173 + ConnectPoint dst = new ConnectPoint(dstDeviceId, dstPort);
174 +
175 + LinkDescription ld = eth.getEtherType() == Ethernet.TYPE_LLDP ?
176 + new DefaultLinkDescription(src, dst, Type.DIRECT, DefaultLinkDescription.EXPECTED) :
177 + new DefaultLinkDescription(src, dst, Type.INDIRECT, DefaultLinkDescription.EXPECTED);
178 +
179 + try {
180 + context.providerService().linkDetected(ld);
181 + context.touchLink(LinkKey.linkKey(src, dst));
182 + } catch (IllegalStateException e) {
183 + return true;
184 + }
185 + return true;
186 + }
187 + return false;
188 + }
189 +
190 +
191 + /**
192 + * Execute this method every t milliseconds. Loops over all ports
193 + * labeled as fast and sends out an LLDP. Send out an LLDP on a single slow
194 + * port.
195 + *
196 + * @param t timeout
197 + */
198 + @Override
199 + public void run(Timeout t) {
200 + if (isStopped()) {
201 + return;
202 + }
203 +
204 + if (context.mastershipService().isLocalMaster(device.id())) {
205 + log.trace("Sending probes from {}", device.id());
206 + ports.forEach(this::sendProbes);
207 + }
208 +
209 + if (!isStopped()) {
210 + timeout = Timer.getTimer().newTimeout(this, context.probeRate(), MILLISECONDS);
211 + }
212 + }
213 +
214 + /**
215 + * Creates packet_out LLDP for specified output port.
216 + *
217 + * @param port the port
218 + * @return Packet_out message with LLDP data
219 + */
220 + private OutboundPacket createOutBoundLldp(Long port) {
221 + if (port == null) {
222 + return null;
223 + }
224 + lldpPacket.setPortId(port.intValue());
225 + ethPacket.setSourceMACAddress(SRC_MAC);
226 + return new DefaultOutboundPacket(device.id(),
227 + builder().setOutput(portNumber(port)).build(),
228 + ByteBuffer.wrap(ethPacket.serialize()));
229 + }
230 +
231 + /**
232 + * Creates packet_out BDDP for specified output port.
233 + *
234 + * @param port the port
235 + * @return Packet_out message with LLDP data
236 + */
237 + private OutboundPacket createOutBoundBddp(Long port) {
238 + if (port == null) {
239 + return null;
240 + }
241 + lldpPacket.setPortId(port.intValue());
242 + bddpEth.setSourceMACAddress(SRC_MAC);
243 + return new DefaultOutboundPacket(device.id(),
244 + builder().setOutput(portNumber(port)).build(),
245 + ByteBuffer.wrap(bddpEth.serialize()));
246 + }
247 +
248 + private void sendProbes(Long portNumber) {
249 + log.trace("Sending probes out to {}@{}", portNumber, device.id());
250 + OutboundPacket pkt = createOutBoundLldp(portNumber);
251 + context.packetService().emit(pkt);
252 + if (context.useBddp()) {
253 + OutboundPacket bpkt = createOutBoundBddp(portNumber);
254 + context.packetService().emit(bpkt);
255 + }
256 + }
257 +
258 + boolean containsPort(long portNumber) {
259 + return ports.contains(portNumber);
260 + }
261 +
262 +}
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +/**
18 + * Provider to pre-discover links and devices based on a specified network
19 + * config.
20 + */
21 +package org.onosproject.provider.netcfglinks;
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
43 <module>bgp</module> 43 <module>bgp</module>
44 <module>snmp</module> 44 <module>snmp</module>
45 <module>rest</module> 45 <module>rest</module>
46 + <module>netcfglinks</module>
46 </modules> 47 </modules>
47 48
48 <dependencies> 49 <dependencies>
......
...@@ -71,7 +71,7 @@ public class ONOSLLDP extends LLDP { ...@@ -71,7 +71,7 @@ public class ONOSLLDP extends LLDP {
71 private final byte[] ttlValue = new byte[] {0, 0x78}; 71 private final byte[] ttlValue = new byte[] {0, 0x78};
72 72
73 // Only needs to be accessed from LinkProbeFactory. 73 // Only needs to be accessed from LinkProbeFactory.
74 - protected ONOSLLDP(byte ... subtype) { 74 + public ONOSLLDP(byte ... subtype) {
75 super(); 75 super();
76 for (byte st : subtype) { 76 for (byte st : subtype) {
77 opttlvs.put(st, new LLDPOrganizationalTLV()); 77 opttlvs.put(st, new LLDPOrganizationalTLV());
......
...@@ -278,6 +278,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler { ...@@ -278,6 +278,7 @@ public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
278 ObjectNode payload = objectNode() 278 ObjectNode payload = objectNode()
279 .put("id", compactLinkString(link)) 279 .put("id", compactLinkString(link))
280 .put("type", link.type().toString().toLowerCase()) 280 .put("type", link.type().toString().toLowerCase())
281 + .put("expected", link.isExpected())
281 .put("online", link.state() == Link.State.ACTIVE) 282 .put("online", link.state() == Link.State.ACTIVE)
282 .put("linkWidth", 1.2) 283 .put("linkWidth", 1.2)
283 .put("src", link.src().deviceId().toString()) 284 .put("src", link.src().deviceId().toString())
......
...@@ -526,6 +526,12 @@ html[data-platform='iPad'] #topo-p-detail { ...@@ -526,6 +526,12 @@ html[data-platform='iPad'] #topo-p-detail {
526 opacity: .5; 526 opacity: .5;
527 stroke-dasharray: 8 4; 527 stroke-dasharray: 8 4;
528 } 528 }
529 +/* FIXME: Review for not-permitted links */
530 +#ov-topo svg .link.not-permitted {
531 + stroke: rgb(255,0,0);
532 + stroke-width: 5.0;
533 + stroke-dasharray: 8 4;
534 +}
529 535
530 #ov-topo svg .link.secondary { 536 #ov-topo svg .link.secondary {
531 stroke-width: 3px; 537 stroke-width: 3px;
......
...@@ -323,7 +323,8 @@ ...@@ -323,7 +323,8 @@
323 .domain([1, 12]) 323 .domain([1, 12])
324 .range([widthRatio, 12 * widthRatio]) 324 .range([widthRatio, 12 * widthRatio])
325 .clamp(true), 325 .clamp(true),
326 - allLinkTypes = 'direct indirect optical tunnel'; 326 + allLinkTypes = 'direct indirect optical tunnel',
327 + allLinkSubTypes = 'inactive not-permitted';
327 328
328 function restyleLinkElement(ldata, immediate) { 329 function restyleLinkElement(ldata, immediate) {
329 // this fn's job is to look at raw links and decide what svg classes 330 // this fn's job is to look at raw links and decide what svg classes
...@@ -333,6 +334,7 @@ ...@@ -333,6 +334,7 @@
333 type = ldata.type(), 334 type = ldata.type(),
334 lw = ldata.linkWidth(), 335 lw = ldata.linkWidth(),
335 online = ldata.online(), 336 online = ldata.online(),
337 + modeCls = ldata.expected() ? 'inactive' : 'not-permitted',
336 delay = immediate ? 0 : 1000; 338 delay = immediate ? 0 : 1000;
337 339
338 // FIXME: understand why el is sometimes undefined on addLink events... 340 // FIXME: understand why el is sometimes undefined on addLink events...
...@@ -343,7 +345,8 @@ ...@@ -343,7 +345,8 @@
343 // a more efficient way to fix it. 345 // a more efficient way to fix it.
344 if (el && !el.empty()) { 346 if (el && !el.empty()) {
345 el.classed('link', true); 347 el.classed('link', true);
346 - el.classed('inactive', !online); 348 + el.classed(allLinkSubTypes, false);
349 + el.classed(modeCls, !online);
347 el.classed(allLinkTypes, false); 350 el.classed(allLinkTypes, false);
348 if (type) { 351 if (type) {
349 el.classed(type, true); 352 el.classed(type, true);
......
...@@ -198,6 +198,11 @@ ...@@ -198,6 +198,11 @@
198 t = lnk.fromTarget; 198 t = lnk.fromTarget;
199 return (s && s.type) || (t && t.type) || defaultLinkType; 199 return (s && s.type) || (t && t.type) || defaultLinkType;
200 }, 200 },
201 + expected: function () {
202 + var s = lnk.fromSource,
203 + t = lnk.fromTarget;
204 + return (s && s.expected) && (t && t.expected);
205 + },
201 online: function () { 206 online: function () {
202 var s = lnk.fromSource, 207 var s = lnk.fromSource,
203 t = lnk.fromTarget, 208 t = lnk.fromTarget,
......
...@@ -301,8 +301,12 @@ ...@@ -301,8 +301,12 @@
301 return linkTypePres[d.type()] || d.type(); 301 return linkTypePres[d.type()] || d.type();
302 } 302 }
303 303
304 + function linkExpected(d) {
305 + return d.expected();
306 + }
307 +
304 var coreOrder = [ 308 var coreOrder = [
305 - 'Type', '-', 309 + 'Type', 'Expected', '-',
306 'A_type', 'A_id', 'A_label', 'A_port', '-', 310 'A_type', 'A_id', 'A_label', 'A_port', '-',
307 'B_type', 'B_id', 'B_label', 'B_port', '-' 311 'B_type', 'B_id', 'B_label', 'B_port', '-'
308 ], 312 ],
...@@ -332,6 +336,7 @@ ...@@ -332,6 +336,7 @@
332 propOrder: order, 336 propOrder: order,
333 props: { 337 props: {
334 Type: linkType(data), 338 Type: linkType(data),
339 + Expected: linkExpected(data),
335 340
336 A_type: data.source.class, 341 A_type: data.source.class,
337 A_id: data.source.id, 342 A_id: data.source.id,
......