Showing
7 changed files
with
233 additions
and
30 deletions
... | @@ -20,6 +20,13 @@ public interface TopologyService { | ... | @@ -20,6 +20,13 @@ public interface TopologyService { |
20 | Topology currentTopology(); | 20 | Topology currentTopology(); |
21 | 21 | ||
22 | /** | 22 | /** |
23 | + * Indicates whether the specified topology is the latest or not. | ||
24 | + * @param topology topology descriptor | ||
25 | + * @return true if the topology is the most recent; false otherwise | ||
26 | + */ | ||
27 | + boolean isLatest(Topology topology); | ||
28 | + | ||
29 | + /** | ||
23 | * Returns the set of clusters in the specified topology. | 30 | * Returns the set of clusters in the specified topology. |
24 | * | 31 | * |
25 | * @param topology topology descriptor | 32 | * @param topology topology descriptor | ... | ... |
1 | +package org.onlab.onos.net.trivial.impl; | ||
2 | + | ||
3 | +import org.onlab.onos.net.AbstractModel; | ||
4 | +import org.onlab.onos.net.provider.ProviderId; | ||
5 | +import org.onlab.onos.net.topology.Topology; | ||
6 | + | ||
7 | +/** | ||
8 | + * Default implementation of the topology descriptor. This carries the | ||
9 | + * backing topology data. | ||
10 | + */ | ||
11 | +public class DefaultTopology extends AbstractModel implements Topology { | ||
12 | + | ||
13 | + private final long time; | ||
14 | + private final int clusterCount; | ||
15 | + private final int deviceCount; | ||
16 | + private final int linkCount; | ||
17 | + private final int pathCount; | ||
18 | + | ||
19 | + /** | ||
20 | + * Creates a topology descriptor attributed to the specified provider. | ||
21 | + * | ||
22 | + * @param providerId identity of the provider | ||
23 | + * @param time creation time in system nanos | ||
24 | + * @param clusterCount number of clusters | ||
25 | + * @param deviceCount number of devices | ||
26 | + * @param linkCount number of links | ||
27 | + * @param pathCount number of pre-computed paths | ||
28 | + */ | ||
29 | + DefaultTopology(ProviderId providerId, long time, int clusterCount, | ||
30 | + int deviceCount, int linkCount, int pathCount) { | ||
31 | + super(providerId); | ||
32 | + this.time = time; | ||
33 | + this.clusterCount = clusterCount; | ||
34 | + this.deviceCount = deviceCount; | ||
35 | + this.linkCount = linkCount; | ||
36 | + this.pathCount = pathCount; | ||
37 | + } | ||
38 | + | ||
39 | + @Override | ||
40 | + public long time() { | ||
41 | + return time; | ||
42 | + } | ||
43 | + | ||
44 | + @Override | ||
45 | + public int clusterCount() { | ||
46 | + return clusterCount; | ||
47 | + } | ||
48 | + | ||
49 | + @Override | ||
50 | + public int deviceCount() { | ||
51 | + return deviceCount; | ||
52 | + } | ||
53 | + | ||
54 | + @Override | ||
55 | + public int linkCount() { | ||
56 | + return linkCount; | ||
57 | + } | ||
58 | + | ||
59 | + @Override | ||
60 | + public int pathCount() { | ||
61 | + return pathCount; | ||
62 | + } | ||
63 | + | ||
64 | +} |
... | @@ -150,7 +150,8 @@ public class SimpleDeviceManager | ... | @@ -150,7 +150,8 @@ public class SimpleDeviceManager |
150 | } | 150 | } |
151 | 151 | ||
152 | // Personalized device provider service issued to the supplied provider. | 152 | // Personalized device provider service issued to the supplied provider. |
153 | - private class InternalDeviceProviderService extends AbstractProviderService<DeviceProvider> | 153 | + private class InternalDeviceProviderService |
154 | + extends AbstractProviderService<DeviceProvider> | ||
154 | implements DeviceProviderService { | 155 | implements DeviceProviderService { |
155 | 156 | ||
156 | InternalDeviceProviderService(DeviceProvider provider) { | 157 | InternalDeviceProviderService(DeviceProvider provider) { | ... | ... |
... | @@ -158,7 +158,7 @@ public class SimpleHostManager | ... | @@ -158,7 +158,7 @@ public class SimpleHostManager |
158 | 158 | ||
159 | // Posts the specified event to the local event dispatcher. | 159 | // Posts the specified event to the local event dispatcher. |
160 | private void post(HostEvent event) { | 160 | private void post(HostEvent event) { |
161 | - if (event != null && eventDispatcher != null) { | 161 | + if (event != null) { |
162 | eventDispatcher.post(event); | 162 | eventDispatcher.post(event); |
163 | } | 163 | } |
164 | } | 164 | } | ... | ... |
... | @@ -36,8 +36,8 @@ import com.google.common.collect.Sets; | ... | @@ -36,8 +36,8 @@ import com.google.common.collect.Sets; |
36 | @Component(immediate = true) | 36 | @Component(immediate = true) |
37 | @Service | 37 | @Service |
38 | public class SimpleLinkManager | 38 | public class SimpleLinkManager |
39 | -extends AbstractProviderRegistry<LinkProvider, LinkProviderService> | 39 | + extends AbstractProviderRegistry<LinkProvider, LinkProviderService> |
40 | -implements LinkService, LinkAdminService, LinkProviderRegistry { | 40 | + implements LinkService, LinkAdminService, LinkProviderRegistry { |
41 | 41 | ||
42 | private static final String DEVICE_ID_NULL = "Device ID cannot be null"; | 42 | private static final String DEVICE_ID_NULL = "Device ID cannot be null"; |
43 | private static final String LINK_DESC_NULL = "Link description cannot be null"; | 43 | private static final String LINK_DESC_NULL = "Link description cannot be null"; |
... | @@ -46,7 +46,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { | ... | @@ -46,7 +46,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { |
46 | private final Logger log = getLogger(getClass()); | 46 | private final Logger log = getLogger(getClass()); |
47 | 47 | ||
48 | private final AbstractListenerRegistry<LinkEvent, LinkListener> | 48 | private final AbstractListenerRegistry<LinkEvent, LinkListener> |
49 | - listenerRegistry = new AbstractListenerRegistry<>(); | 49 | + listenerRegistry = new AbstractListenerRegistry<>(); |
50 | 50 | ||
51 | private final SimpleLinkStore store = new SimpleLinkStore(); | 51 | private final SimpleLinkStore store = new SimpleLinkStore(); |
52 | 52 | ||
... | @@ -79,7 +79,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { | ... | @@ -79,7 +79,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { |
79 | public Set<Link> getDeviceLinks(DeviceId deviceId) { | 79 | public Set<Link> getDeviceLinks(DeviceId deviceId) { |
80 | checkNotNull(deviceId, DEVICE_ID_NULL); | 80 | checkNotNull(deviceId, DEVICE_ID_NULL); |
81 | return Sets.union(store.getDeviceEgressLinks(deviceId), | 81 | return Sets.union(store.getDeviceEgressLinks(deviceId), |
82 | - store.getDeviceIngressLinks(deviceId)); | 82 | + store.getDeviceIngressLinks(deviceId)); |
83 | } | 83 | } |
84 | 84 | ||
85 | @Override | 85 | @Override |
... | @@ -98,7 +98,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { | ... | @@ -98,7 +98,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { |
98 | public Set<Link> getLinks(ConnectPoint connectPoint) { | 98 | public Set<Link> getLinks(ConnectPoint connectPoint) { |
99 | checkNotNull(connectPoint, CONNECT_POINT_NULL); | 99 | checkNotNull(connectPoint, CONNECT_POINT_NULL); |
100 | return Sets.union(store.getEgressLinks(connectPoint), | 100 | return Sets.union(store.getEgressLinks(connectPoint), |
101 | - store.getIngressLinks(connectPoint)); | 101 | + store.getIngressLinks(connectPoint)); |
102 | } | 102 | } |
103 | 103 | ||
104 | @Override | 104 | @Override |
... | @@ -146,8 +146,9 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { | ... | @@ -146,8 +146,9 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { |
146 | } | 146 | } |
147 | 147 | ||
148 | // Personalized link provider service issued to the supplied provider. | 148 | // Personalized link provider service issued to the supplied provider. |
149 | - private class InternalLinkProviderService extends AbstractProviderService<LinkProvider> | 149 | + private class InternalLinkProviderService |
150 | - implements LinkProviderService { | 150 | + extends AbstractProviderService<LinkProvider> |
151 | + implements LinkProviderService { | ||
151 | 152 | ||
152 | InternalLinkProviderService(LinkProvider provider) { | 153 | InternalLinkProviderService(LinkProvider provider) { |
153 | super(provider); | 154 | super(provider); |
... | @@ -157,27 +158,31 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { | ... | @@ -157,27 +158,31 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { |
157 | public void linkDetected(LinkDescription linkDescription) { | 158 | public void linkDetected(LinkDescription linkDescription) { |
158 | checkNotNull(linkDescription, LINK_DESC_NULL); | 159 | checkNotNull(linkDescription, LINK_DESC_NULL); |
159 | checkValidity(); | 160 | checkValidity(); |
160 | - log.debug("Link {} detected", linkDescription); | ||
161 | LinkEvent event = store.createOrUpdateLink(provider().id(), | 161 | LinkEvent event = store.createOrUpdateLink(provider().id(), |
162 | - linkDescription); | 162 | + linkDescription); |
163 | - post(event); | 163 | + if (event != null) { |
164 | + log.debug("Link {} detected", linkDescription); | ||
165 | + post(event); | ||
166 | + } | ||
164 | } | 167 | } |
165 | 168 | ||
166 | @Override | 169 | @Override |
167 | public void linkVanished(LinkDescription linkDescription) { | 170 | public void linkVanished(LinkDescription linkDescription) { |
168 | checkNotNull(linkDescription, LINK_DESC_NULL); | 171 | checkNotNull(linkDescription, LINK_DESC_NULL); |
169 | checkValidity(); | 172 | checkValidity(); |
170 | - log.info("Link {} vanished", linkDescription); | ||
171 | LinkEvent event = store.removeLink(linkDescription.src(), | 173 | LinkEvent event = store.removeLink(linkDescription.src(), |
172 | - linkDescription.dst()); | 174 | + linkDescription.dst()); |
173 | - post(event); | 175 | + if (event != null) { |
176 | + log.info("Link {} vanished", linkDescription); | ||
177 | + post(event); | ||
178 | + } | ||
174 | } | 179 | } |
175 | 180 | ||
176 | @Override | 181 | @Override |
177 | public void linksVanished(ConnectPoint connectPoint) { | 182 | public void linksVanished(ConnectPoint connectPoint) { |
178 | checkNotNull(connectPoint, "Connect point cannot be null"); | 183 | checkNotNull(connectPoint, "Connect point cannot be null"); |
179 | checkValidity(); | 184 | checkValidity(); |
180 | - log.info("Link for connection point {} vanished", connectPoint); | 185 | + log.info("Links for connection point {} vanished", connectPoint); |
181 | removeLinks(getLinks(connectPoint)); | 186 | removeLinks(getLinks(connectPoint)); |
182 | } | 187 | } |
183 | 188 | ||
... | @@ -185,7 +190,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { | ... | @@ -185,7 +190,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { |
185 | public void linksVanished(DeviceId deviceId) { | 190 | public void linksVanished(DeviceId deviceId) { |
186 | checkNotNull(deviceId, DEVICE_ID_NULL); | 191 | checkNotNull(deviceId, DEVICE_ID_NULL); |
187 | checkValidity(); | 192 | checkValidity(); |
188 | - log.info("Link for device {} vanished", deviceId); | 193 | + log.info("Links for device {} vanished", deviceId); |
189 | removeLinks(getDeviceLinks(deviceId)); | 194 | removeLinks(getDeviceLinks(deviceId)); |
190 | } | 195 | } |
191 | } | 196 | } |
... | @@ -200,7 +205,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { | ... | @@ -200,7 +205,7 @@ implements LinkService, LinkAdminService, LinkProviderRegistry { |
200 | 205 | ||
201 | // Posts the specified event to the local event dispatcher. | 206 | // Posts the specified event to the local event dispatcher. |
202 | private void post(LinkEvent event) { | 207 | private void post(LinkEvent event) { |
203 | - if (event != null && eventDispatcher != null) { | 208 | + if (event != null) { |
204 | eventDispatcher.post(event); | 209 | eventDispatcher.post(event); |
205 | } | 210 | } |
206 | } | 211 | } | ... | ... |
... | @@ -72,25 +72,26 @@ public class SimpleTopologyManager | ... | @@ -72,25 +72,26 @@ public class SimpleTopologyManager |
72 | } | 72 | } |
73 | 73 | ||
74 | @Override | 74 | @Override |
75 | - protected TopologyProviderService createProviderService(TopologyProvider provider) { | 75 | + public Topology currentTopology() { |
76 | - return new InternalTopologyProviderService(provider); | 76 | + return store.currentTopology(); |
77 | } | 77 | } |
78 | 78 | ||
79 | @Override | 79 | @Override |
80 | - public Topology currentTopology() { | 80 | + public boolean isLatest(Topology topology) { |
81 | - return null; | 81 | + checkNotNull(topology, TOPOLOGY_NULL); |
82 | + return store.isLatest(topology); | ||
82 | } | 83 | } |
83 | 84 | ||
84 | @Override | 85 | @Override |
85 | public Set<TopologyCluster> getClusters(Topology topology) { | 86 | public Set<TopologyCluster> getClusters(Topology topology) { |
86 | checkNotNull(topology, TOPOLOGY_NULL); | 87 | checkNotNull(topology, TOPOLOGY_NULL); |
87 | - return null; | 88 | + return store.getClusters(topology); |
88 | } | 89 | } |
89 | 90 | ||
90 | @Override | 91 | @Override |
91 | public Graph<TopoVertex, TopoEdge> getGraph(Topology topology) { | 92 | public Graph<TopoVertex, TopoEdge> getGraph(Topology topology) { |
92 | checkNotNull(topology, TOPOLOGY_NULL); | 93 | checkNotNull(topology, TOPOLOGY_NULL); |
93 | - return null; | 94 | + return store.getGraph(topology); |
94 | } | 95 | } |
95 | 96 | ||
96 | @Override | 97 | @Override |
... | @@ -98,7 +99,7 @@ public class SimpleTopologyManager | ... | @@ -98,7 +99,7 @@ public class SimpleTopologyManager |
98 | checkNotNull(topology, TOPOLOGY_NULL); | 99 | checkNotNull(topology, TOPOLOGY_NULL); |
99 | checkNotNull(src, DEVICE_ID_NULL); | 100 | checkNotNull(src, DEVICE_ID_NULL); |
100 | checkNotNull(dst, DEVICE_ID_NULL); | 101 | checkNotNull(dst, DEVICE_ID_NULL); |
101 | - return null; | 102 | + return store.getPaths(topology, src, dst); |
102 | } | 103 | } |
103 | 104 | ||
104 | @Override | 105 | @Override |
... | @@ -107,21 +108,21 @@ public class SimpleTopologyManager | ... | @@ -107,21 +108,21 @@ public class SimpleTopologyManager |
107 | checkNotNull(src, DEVICE_ID_NULL); | 108 | checkNotNull(src, DEVICE_ID_NULL); |
108 | checkNotNull(dst, DEVICE_ID_NULL); | 109 | checkNotNull(dst, DEVICE_ID_NULL); |
109 | checkNotNull(weight, "Link weight cannot be null"); | 110 | checkNotNull(weight, "Link weight cannot be null"); |
110 | - return null; | 111 | + return store.getPaths(topology, src, dst, weight); |
111 | } | 112 | } |
112 | 113 | ||
113 | @Override | 114 | @Override |
114 | public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) { | 115 | public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) { |
115 | checkNotNull(topology, TOPOLOGY_NULL); | 116 | checkNotNull(topology, TOPOLOGY_NULL); |
116 | checkNotNull(connectPoint, CONNECTION_POINT_NULL); | 117 | checkNotNull(connectPoint, CONNECTION_POINT_NULL); |
117 | - return false; | 118 | + return store.isInfrastructure(topology, connectPoint); |
118 | } | 119 | } |
119 | 120 | ||
120 | @Override | 121 | @Override |
121 | public boolean isInBroadcastTree(Topology topology, ConnectPoint connectPoint) { | 122 | public boolean isInBroadcastTree(Topology topology, ConnectPoint connectPoint) { |
122 | checkNotNull(topology, TOPOLOGY_NULL); | 123 | checkNotNull(topology, TOPOLOGY_NULL); |
123 | checkNotNull(connectPoint, CONNECTION_POINT_NULL); | 124 | checkNotNull(connectPoint, CONNECTION_POINT_NULL); |
124 | - return false; | 125 | + return store.isInBroadcastTree(topology, connectPoint); |
125 | } | 126 | } |
126 | 127 | ||
127 | @Override | 128 | @Override |
... | @@ -135,6 +136,11 @@ public class SimpleTopologyManager | ... | @@ -135,6 +136,11 @@ public class SimpleTopologyManager |
135 | } | 136 | } |
136 | 137 | ||
137 | // Personalized host provider service issued to the supplied provider. | 138 | // Personalized host provider service issued to the supplied provider. |
139 | + @Override | ||
140 | + protected TopologyProviderService createProviderService(TopologyProvider provider) { | ||
141 | + return new InternalTopologyProviderService(provider); | ||
142 | + } | ||
143 | + | ||
138 | private class InternalTopologyProviderService | 144 | private class InternalTopologyProviderService |
139 | extends AbstractProviderService<TopologyProvider> | 145 | extends AbstractProviderService<TopologyProvider> |
140 | implements TopologyProviderService { | 146 | implements TopologyProviderService { |
... | @@ -147,8 +153,12 @@ public class SimpleTopologyManager | ... | @@ -147,8 +153,12 @@ public class SimpleTopologyManager |
147 | public void topologyChanged(TopologyDescription topoDescription, | 153 | public void topologyChanged(TopologyDescription topoDescription, |
148 | List<Event> reasons) { | 154 | List<Event> reasons) { |
149 | checkNotNull(topoDescription, "Topology description cannot be null"); | 155 | checkNotNull(topoDescription, "Topology description cannot be null"); |
150 | - log.info("Topology changed due to: {}", | 156 | + TopologyEvent event = store.updateTopology(topoDescription, reasons); |
151 | - reasons == null ? "initial compute" : reasons); | 157 | + if (event != null) { |
158 | + log.info("Topology changed due to: {}", | ||
159 | + reasons == null ? "initial compute" : reasons); | ||
160 | + eventDispatcher.post(event); | ||
161 | + } | ||
152 | } | 162 | } |
153 | } | 163 | } |
154 | 164 | ... | ... |
1 | package org.onlab.onos.net.trivial.impl; | 1 | package org.onlab.onos.net.trivial.impl; |
2 | 2 | ||
3 | +import org.onlab.graph.Graph; | ||
4 | +import org.onlab.onos.event.Event; | ||
5 | +import org.onlab.onos.net.ConnectPoint; | ||
6 | +import org.onlab.onos.net.DeviceId; | ||
7 | +import org.onlab.onos.net.Path; | ||
8 | +import org.onlab.onos.net.topology.LinkWeight; | ||
9 | +import org.onlab.onos.net.topology.TopoEdge; | ||
10 | +import org.onlab.onos.net.topology.TopoVertex; | ||
11 | +import org.onlab.onos.net.topology.Topology; | ||
12 | +import org.onlab.onos.net.topology.TopologyCluster; | ||
13 | +import org.onlab.onos.net.topology.TopologyDescription; | ||
14 | +import org.onlab.onos.net.topology.TopologyEvent; | ||
15 | + | ||
16 | +import java.util.List; | ||
17 | +import java.util.Set; | ||
18 | + | ||
3 | /** | 19 | /** |
4 | * Manages inventory of topology snapshots using trivial in-memory | 20 | * Manages inventory of topology snapshots using trivial in-memory |
5 | * implementation. | 21 | * implementation. |
6 | */ | 22 | */ |
7 | public class SimpleTopologyStore { | 23 | public class SimpleTopologyStore { |
24 | + | ||
25 | + private volatile DefaultTopology current; | ||
26 | + | ||
27 | + /** | ||
28 | + * Returns the current topology snapshot. | ||
29 | + * | ||
30 | + * @return current topology descriptor | ||
31 | + */ | ||
32 | + Topology currentTopology() { | ||
33 | + return current; | ||
34 | + } | ||
35 | + | ||
36 | + /** | ||
37 | + * Indicates whether the topology is the latest. | ||
38 | + * @param topology topology descriptor | ||
39 | + * @return true if topology is the most recent one | ||
40 | + */ | ||
41 | + boolean isLatest(Topology topology) { | ||
42 | + return topology == current; | ||
43 | + } | ||
44 | + | ||
45 | + /** | ||
46 | + * Returns the set of topology SCC clusters. | ||
47 | + * | ||
48 | + * @param topology topology descriptor | ||
49 | + * @return set of clusters | ||
50 | + */ | ||
51 | + Set<TopologyCluster> getClusters(Topology topology) { | ||
52 | + return null; | ||
53 | + } | ||
54 | + | ||
55 | + /** | ||
56 | + * Returns the immutable graph view of the current topology. | ||
57 | + * | ||
58 | + * @param topology topology descriptor | ||
59 | + * @return graph view | ||
60 | + */ | ||
61 | + Graph<TopoVertex, TopoEdge> getGraph(Topology topology) { | ||
62 | + return null; | ||
63 | + } | ||
64 | + | ||
65 | + /** | ||
66 | + * Returns the set of pre-computed shortest paths between src and dest. | ||
67 | + * | ||
68 | + * @param topology topology descriptor | ||
69 | + * @param src source device | ||
70 | + * @param dst destination device | ||
71 | + * @return set of shortest paths | ||
72 | + */ | ||
73 | + Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) { | ||
74 | + return null; | ||
75 | + } | ||
76 | + | ||
77 | + /** | ||
78 | + * Computes and returns the set of shortest paths between src and dest. | ||
79 | + * | ||
80 | + * @param topology topology descriptor | ||
81 | + * @param src source device | ||
82 | + * @param dst destination device | ||
83 | + * @param weight link weight function | ||
84 | + * @return set of shortest paths | ||
85 | + */ | ||
86 | + Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst, | ||
87 | + LinkWeight weight) { | ||
88 | + return null; | ||
89 | + } | ||
90 | + | ||
91 | + /** | ||
92 | + * Indicates whether the given connect point is part of the network fabric. | ||
93 | + * | ||
94 | + * @param topology topology descriptor | ||
95 | + * @param connectPoint connection point | ||
96 | + * @return true if infrastructure; false otherwise | ||
97 | + */ | ||
98 | + boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) { | ||
99 | + return false; | ||
100 | + } | ||
101 | + | ||
102 | + /** | ||
103 | + * Indicates whether the given connect point is part of the broadcast tree. | ||
104 | + * | ||
105 | + * @param topology topology descriptor | ||
106 | + * @param connectPoint connection point | ||
107 | + * @return true if in broadcast tree; false otherwise | ||
108 | + */ | ||
109 | + boolean isInBroadcastTree(Topology topology, ConnectPoint connectPoint) { | ||
110 | + return false; | ||
111 | + } | ||
112 | + | ||
113 | + /** | ||
114 | + * Generates a new topology snapshot from the specified description. | ||
115 | + * | ||
116 | + * @param topoDescription topology description | ||
117 | + * @param reasons list of events that triggered the update | ||
118 | + * @return topology update event or null if the description is old | ||
119 | + */ | ||
120 | + TopologyEvent updateTopology(TopologyDescription topoDescription, List<Event> reasons) { | ||
121 | + return null; | ||
122 | + } | ||
123 | + | ||
8 | } | 124 | } | ... | ... |
-
Please register or login to post a comment