Committed by
Gerrit Code Review
Add MPLS encapsulation behaviour ONOS-3467
- MPLS encapsulation using constraint - New MPLS encapsulation test - Fix VLAN encapsulation test Change-Id: I94670bcd51a95a0272f786681e51d6785a56c4f5
Showing
8 changed files
with
223 additions
and
7 deletions
| ... | @@ -31,7 +31,11 @@ import java.util.Optional; | ... | @@ -31,7 +31,11 @@ import java.util.Optional; |
| 31 | 31 | ||
| 32 | /** | 32 | /** |
| 33 | * Installs MPLS intents. | 33 | * Installs MPLS intents. |
| 34 | + * | ||
| 35 | + * @deprecated in Goldeneye Release, in favour of encapsulation | ||
| 36 | + * constraint {@link org.onosproject.net.intent.constraint.EncapsulationConstraint} | ||
| 34 | */ | 37 | */ |
| 38 | +@Deprecated | ||
| 35 | @Command(scope = "onos", name = "add-mpls-intent", description = "Installs mpls connectivity intent") | 39 | @Command(scope = "onos", name = "add-mpls-intent", description = "Installs mpls connectivity intent") |
| 36 | public class AddMplsIntent extends ConnectivityIntentCommand { | 40 | public class AddMplsIntent extends ConnectivityIntentCommand { |
| 37 | 41 | ... | ... |
| ... | @@ -34,8 +34,14 @@ import static com.google.common.base.Preconditions.checkNotNull; | ... | @@ -34,8 +34,14 @@ import static com.google.common.base.Preconditions.checkNotNull; |
| 34 | 34 | ||
| 35 | /** | 35 | /** |
| 36 | * Abstraction of MPLS label-switched connectivity. | 36 | * Abstraction of MPLS label-switched connectivity. |
| 37 | + * | ||
| 38 | + * @deprecated in Goldeneye Release, in favour of encapsulation | ||
| 39 | + * constraint {@link org.onosproject.net.intent.constraint.EncapsulationConstraint} | ||
| 40 | + * with Encasulation type {@link org.onosproject.net.EncapsulationType} MPLS. | ||
| 41 | + * | ||
| 37 | */ | 42 | */ |
| 38 | @Beta | 43 | @Beta |
| 44 | +@Deprecated | ||
| 39 | public final class MplsIntent extends ConnectivityIntent { | 45 | public final class MplsIntent extends ConnectivityIntent { |
| 40 | 46 | ||
| 41 | private final ConnectPoint ingressPoint; | 47 | private final ConnectPoint ingressPoint; | ... | ... |
| ... | @@ -30,8 +30,12 @@ import static com.google.common.base.Preconditions.checkNotNull; | ... | @@ -30,8 +30,12 @@ import static com.google.common.base.Preconditions.checkNotNull; |
| 30 | 30 | ||
| 31 | /** | 31 | /** |
| 32 | * Abstraction of explicit MPLS label-switched path. | 32 | * Abstraction of explicit MPLS label-switched path. |
| 33 | + * | ||
| 34 | + * @deprecated in Goldeneye Release, in favour of encapsulation | ||
| 35 | + * constraint {@link org.onosproject.net.intent.constraint.EncapsulationConstraint} | ||
| 33 | */ | 36 | */ |
| 34 | @Beta | 37 | @Beta |
| 38 | +@Deprecated | ||
| 35 | public final class MplsPathIntent extends PathIntent { | 39 | public final class MplsPathIntent extends PathIntent { |
| 36 | 40 | ||
| 37 | private final Optional<MplsLabel> ingressLabel; | 41 | private final Optional<MplsLabel> ingressLabel; | ... | ... |
| ... | @@ -36,7 +36,11 @@ import org.onosproject.net.intent.MplsPathIntent; | ... | @@ -36,7 +36,11 @@ import org.onosproject.net.intent.MplsPathIntent; |
| 36 | import org.onosproject.net.provider.ProviderId; | 36 | import org.onosproject.net.provider.ProviderId; |
| 37 | import org.onosproject.net.resource.link.LinkResourceAllocations; | 37 | import org.onosproject.net.resource.link.LinkResourceAllocations; |
| 38 | 38 | ||
| 39 | - | 39 | +/** |
| 40 | + * @deprecated in Goldeneye Release, in favour of encapsulation | ||
| 41 | + * constraint {@link org.onosproject.net.intent.constraint.EncapsulationConstraint} | ||
| 42 | + */ | ||
| 43 | +@Deprecated | ||
| 40 | @Component(immediate = true) | 44 | @Component(immediate = true) |
| 41 | public class MplsIntentCompiler extends ConnectivityIntentCompiler<MplsIntent> { | 45 | public class MplsIntentCompiler extends ConnectivityIntentCompiler<MplsIntent> { |
| 42 | 46 | ... | ... |
| ... | @@ -68,6 +68,11 @@ import static com.google.common.base.Preconditions.checkNotNull; | ... | @@ -68,6 +68,11 @@ import static com.google.common.base.Preconditions.checkNotNull; |
| 68 | import static org.onosproject.net.LinkKey.linkKey; | 68 | import static org.onosproject.net.LinkKey.linkKey; |
| 69 | import static org.slf4j.LoggerFactory.getLogger; | 69 | import static org.slf4j.LoggerFactory.getLogger; |
| 70 | 70 | ||
| 71 | +/** | ||
| 72 | + * @deprecated in Goldeneye Release, in favour of encapsulation | ||
| 73 | + * constraint {@link org.onosproject.net.intent.constraint.EncapsulationConstraint} | ||
| 74 | + */ | ||
| 75 | +@Deprecated | ||
| 71 | @Component(immediate = true) | 76 | @Component(immediate = true) |
| 72 | public class MplsPathIntentCompiler implements IntentCompiler<MplsPathIntent> { | 77 | public class MplsPathIntentCompiler implements IntentCompiler<MplsPathIntent> { |
| 73 | 78 | ... | ... |
| ... | @@ -25,6 +25,9 @@ import java.util.Set; | ... | @@ -25,6 +25,9 @@ import java.util.Set; |
| 25 | import java.util.stream.Collectors; | 25 | import java.util.stream.Collectors; |
| 26 | import java.util.stream.Stream; | 26 | import java.util.stream.Stream; |
| 27 | 27 | ||
| 28 | +import org.onlab.packet.EthType; | ||
| 29 | +import org.onlab.packet.Ethernet; | ||
| 30 | +import org.onlab.packet.MplsLabel; | ||
| 28 | import org.onlab.packet.VlanId; | 31 | import org.onlab.packet.VlanId; |
| 29 | import org.onosproject.net.ConnectPoint; | 32 | import org.onosproject.net.ConnectPoint; |
| 30 | import org.onosproject.net.DeviceId; | 33 | import org.onosproject.net.DeviceId; |
| ... | @@ -35,12 +38,16 @@ import org.onosproject.net.flow.DefaultTrafficTreatment; | ... | @@ -35,12 +38,16 @@ import org.onosproject.net.flow.DefaultTrafficTreatment; |
| 35 | import org.onosproject.net.flow.TrafficSelector; | 38 | import org.onosproject.net.flow.TrafficSelector; |
| 36 | import org.onosproject.net.flow.TrafficTreatment; | 39 | import org.onosproject.net.flow.TrafficTreatment; |
| 37 | import org.onosproject.net.flow.criteria.Criterion; | 40 | import org.onosproject.net.flow.criteria.Criterion; |
| 41 | +import org.onosproject.net.flow.criteria.EthTypeCriterion; | ||
| 42 | +import org.onosproject.net.flow.criteria.MplsCriterion; | ||
| 38 | import org.onosproject.net.flow.criteria.VlanIdCriterion; | 43 | import org.onosproject.net.flow.criteria.VlanIdCriterion; |
| 44 | +import org.onosproject.net.flow.instructions.Instruction; | ||
| 39 | import org.onosproject.net.flow.instructions.L2ModificationInstruction; | 45 | import org.onosproject.net.flow.instructions.L2ModificationInstruction; |
| 40 | import org.onosproject.net.intent.PathIntent; | 46 | import org.onosproject.net.intent.PathIntent; |
| 41 | import org.onosproject.net.intent.constraint.EncapsulationConstraint; | 47 | import org.onosproject.net.intent.constraint.EncapsulationConstraint; |
| 42 | import org.onosproject.net.intent.impl.IntentCompilationException; | 48 | import org.onosproject.net.intent.impl.IntentCompilationException; |
| 43 | import org.onosproject.net.newresource.Resource; | 49 | import org.onosproject.net.newresource.Resource; |
| 50 | +import org.onosproject.net.newresource.ResourceAllocation; | ||
| 44 | import org.onosproject.net.newresource.ResourceService; | 51 | import org.onosproject.net.newresource.ResourceService; |
| 45 | import org.onosproject.net.newresource.Resources; | 52 | import org.onosproject.net.newresource.Resources; |
| 46 | import org.slf4j.Logger; | 53 | import org.slf4j.Logger; |
| ... | @@ -227,6 +234,185 @@ public class PathCompiler<T> { | ... | @@ -227,6 +234,185 @@ public class PathCompiler<T> { |
| 227 | } | 234 | } |
| 228 | } | 235 | } |
| 229 | 236 | ||
| 237 | + private Map<LinkKey, MplsLabel> assignMplsLabel(PathCompilerCreateFlow creator, PathIntent intent) { | ||
| 238 | + Set<LinkKey> linkRequest = | ||
| 239 | + Sets.newHashSetWithExpectedSize(intent.path() | ||
| 240 | + .links().size() - 2); | ||
| 241 | + for (int i = 1; i <= intent.path().links().size() - 2; i++) { | ||
| 242 | + LinkKey link = linkKey(intent.path().links().get(i)); | ||
| 243 | + linkRequest.add(link); | ||
| 244 | + // add the inverse link. I want that the VLANID is reserved both for | ||
| 245 | + // the direct and inverse link | ||
| 246 | + linkRequest.add(linkKey(link.dst(), link.src())); | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + Map<LinkKey, MplsLabel> labels = findMplsLabels(creator, linkRequest); | ||
| 250 | + if (labels.isEmpty()) { | ||
| 251 | + throw new IntentCompilationException("No available MPLS Label"); | ||
| 252 | + } | ||
| 253 | + | ||
| 254 | + // for short term solution: same label is used for both directions | ||
| 255 | + // TODO: introduce the concept of Tx and Rx resources of a port | ||
| 256 | + Set<Resource> resources = labels.entrySet().stream() | ||
| 257 | + .flatMap(x -> Stream.of( | ||
| 258 | + Resources.discrete(x.getKey().src().deviceId(), x.getKey().src().port(), x.getValue()) | ||
| 259 | + .resource(), | ||
| 260 | + Resources.discrete(x.getKey().dst().deviceId(), x.getKey().dst().port(), x.getValue()) | ||
| 261 | + .resource() | ||
| 262 | + )) | ||
| 263 | + .collect(Collectors.toSet()); | ||
| 264 | + List<ResourceAllocation> allocations = | ||
| 265 | + creator.resourceService().allocate(intent.id(), ImmutableList.copyOf(resources)); | ||
| 266 | + if (allocations.isEmpty()) { | ||
| 267 | + Collections.emptyMap(); | ||
| 268 | + } | ||
| 269 | + | ||
| 270 | + return labels; | ||
| 271 | + } | ||
| 272 | + | ||
| 273 | + private Map<LinkKey, MplsLabel> findMplsLabels(PathCompilerCreateFlow creator, Set<LinkKey> links) { | ||
| 274 | + Map<LinkKey, MplsLabel> labels = new HashMap<>(); | ||
| 275 | + for (LinkKey link : links) { | ||
| 276 | + Set<MplsLabel> forward = findMplsLabel(creator, link.src()); | ||
| 277 | + Set<MplsLabel> backward = findMplsLabel(creator, link.dst()); | ||
| 278 | + Set<MplsLabel> common = Sets.intersection(forward, backward); | ||
| 279 | + if (common.isEmpty()) { | ||
| 280 | + continue; | ||
| 281 | + } | ||
| 282 | + labels.put(link, common.iterator().next()); | ||
| 283 | + } | ||
| 284 | + | ||
| 285 | + return labels; | ||
| 286 | + } | ||
| 287 | + | ||
| 288 | + private Set<MplsLabel> findMplsLabel(PathCompilerCreateFlow creator, ConnectPoint cp) { | ||
| 289 | + return creator.resourceService().getAvailableResourceValues( | ||
| 290 | + Resources.discrete(cp.deviceId(), cp.port()).id(), | ||
| 291 | + MplsLabel.class); | ||
| 292 | + } | ||
| 293 | + | ||
| 294 | + private void manageMplsEncap(PathCompilerCreateFlow<T> creator, List<T> flows, | ||
| 295 | + List<DeviceId> devices, | ||
| 296 | + PathIntent intent) { | ||
| 297 | + Map<LinkKey, MplsLabel> mplsLabels = assignMplsLabel(creator, intent); | ||
| 298 | + | ||
| 299 | + Iterator<Link> links = intent.path().links().iterator(); | ||
| 300 | + Link srcLink = links.next(); | ||
| 301 | + | ||
| 302 | + Link link = links.next(); | ||
| 303 | + // List of flow rules to be installed | ||
| 304 | + | ||
| 305 | + // Ingress traffic | ||
| 306 | + MplsLabel mplsLabel = mplsLabels.get(linkKey(link)); | ||
| 307 | + if (mplsLabel == null) { | ||
| 308 | + throw new IntentCompilationException("No available MPLS Label for " + link); | ||
| 309 | + } | ||
| 310 | + MplsLabel prevMplsLabel = mplsLabel; | ||
| 311 | + | ||
| 312 | + Optional<MplsCriterion> mplsCriterion = intent.selector().criteria() | ||
| 313 | + .stream().filter(criterion -> criterion.type() == Criterion.Type.MPLS_LABEL) | ||
| 314 | + .map(criterion -> (MplsCriterion) criterion) | ||
| 315 | + .findAny(); | ||
| 316 | + | ||
| 317 | + //Push MPLS if selector does not include MPLS | ||
| 318 | + TrafficTreatment.Builder treatBuilder = DefaultTrafficTreatment.builder(); | ||
| 319 | + if (!mplsCriterion.isPresent()) { | ||
| 320 | + treatBuilder.pushMpls(); | ||
| 321 | + } | ||
| 322 | + //Tag the traffic with the new encapsulation MPLS label | ||
| 323 | + treatBuilder.setMpls(mplsLabel); | ||
| 324 | + creator.createFlow(intent.selector(), treatBuilder.build(), | ||
| 325 | + srcLink.dst(), link.src(), intent.priority(), true, flows, devices); | ||
| 326 | + | ||
| 327 | + ConnectPoint prev = link.dst(); | ||
| 328 | + | ||
| 329 | + while (links.hasNext()) { | ||
| 330 | + | ||
| 331 | + link = links.next(); | ||
| 332 | + | ||
| 333 | + if (links.hasNext()) { | ||
| 334 | + // Transit traffic | ||
| 335 | + MplsLabel transitMplsLabel = mplsLabels.get(linkKey(link)); | ||
| 336 | + if (transitMplsLabel == null) { | ||
| 337 | + throw new IntentCompilationException("No available MPLS label for " + link); | ||
| 338 | + } | ||
| 339 | + prevMplsLabel = transitMplsLabel; | ||
| 340 | + | ||
| 341 | + TrafficSelector transitSelector = DefaultTrafficSelector.builder() | ||
| 342 | + .matchInPort(prev.port()) | ||
| 343 | + .matchEthType(Ethernet.MPLS_UNICAST) | ||
| 344 | + .matchMplsLabel(prevMplsLabel).build(); | ||
| 345 | + | ||
| 346 | + TrafficTreatment.Builder transitTreat = DefaultTrafficTreatment.builder(); | ||
| 347 | + | ||
| 348 | + // Set the new MPLS Label only if the previous one is different | ||
| 349 | + if (!prevMplsLabel.equals(transitMplsLabel)) { | ||
| 350 | + transitTreat.setMpls(transitMplsLabel); | ||
| 351 | + } | ||
| 352 | + creator.createFlow(transitSelector, | ||
| 353 | + transitTreat.build(), prev, link.src(), intent.priority(), true, flows, devices); | ||
| 354 | + prev = link.dst(); | ||
| 355 | + } else { | ||
| 356 | + TrafficSelector.Builder egressSelector = DefaultTrafficSelector.builder() | ||
| 357 | + .matchInPort(prev.port()) | ||
| 358 | + .matchEthType(Ethernet.MPLS_UNICAST) | ||
| 359 | + .matchMplsLabel(prevMplsLabel); | ||
| 360 | + TrafficTreatment.Builder egressTreat = DefaultTrafficTreatment.builder(intent.treatment()); | ||
| 361 | + | ||
| 362 | + // Egress traffic | ||
| 363 | + // check if the treatement is popVlan or setVlan (rewrite), | ||
| 364 | + // than selector needs to match any VlanId | ||
| 365 | + for (Instruction instruct : intent.treatment().allInstructions()) { | ||
| 366 | + if (instruct instanceof L2ModificationInstruction) { | ||
| 367 | + L2ModificationInstruction l2Mod = (L2ModificationInstruction) instruct; | ||
| 368 | + if (l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_PUSH) { | ||
| 369 | + break; | ||
| 370 | + } | ||
| 371 | + if (l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_POP || | ||
| 372 | + l2Mod.subtype() == L2ModificationInstruction.L2SubType.VLAN_ID) { | ||
| 373 | + egressSelector.matchVlanId(VlanId.ANY); | ||
| 374 | + } | ||
| 375 | + } | ||
| 376 | + } | ||
| 377 | + | ||
| 378 | + if (mplsCriterion.isPresent()) { | ||
| 379 | + egressTreat.setMpls(mplsCriterion.get().label()); | ||
| 380 | + } else { | ||
| 381 | + egressTreat.popMpls(outputEthType(intent.selector())); | ||
| 382 | + } | ||
| 383 | + | ||
| 384 | + | ||
| 385 | + if (mplsCriterion.isPresent()) { | ||
| 386 | + egressTreat.setMpls(mplsCriterion.get().label()); | ||
| 387 | + } else { | ||
| 388 | + egressTreat.popVlan(); | ||
| 389 | + } | ||
| 390 | + | ||
| 391 | + creator.createFlow(egressSelector.build(), | ||
| 392 | + egressTreat.build(), prev, link.src(), intent.priority(), true, flows, devices); | ||
| 393 | + } | ||
| 394 | + | ||
| 395 | + } | ||
| 396 | + | ||
| 397 | + } | ||
| 398 | + | ||
| 399 | + private MplsLabel getMplsLabel(Map<LinkKey, MplsLabel> labels, LinkKey link) { | ||
| 400 | + return labels.get(link); | ||
| 401 | + } | ||
| 402 | + | ||
| 403 | + // if the ingress ethertype is defined, the egress traffic | ||
| 404 | + // will be use that value, otherwise the IPv4 ethertype is used. | ||
| 405 | + private EthType outputEthType(TrafficSelector selector) { | ||
| 406 | + Criterion c = selector.getCriterion(Criterion.Type.ETH_TYPE); | ||
| 407 | + if (c != null && c instanceof EthTypeCriterion) { | ||
| 408 | + EthTypeCriterion ethertype = (EthTypeCriterion) c; | ||
| 409 | + return ethertype.ethType(); | ||
| 410 | + } else { | ||
| 411 | + return EthType.EtherType.IPV4.ethType(); | ||
| 412 | + } | ||
| 413 | + } | ||
| 414 | + | ||
| 415 | + | ||
| 230 | /** | 416 | /** |
| 231 | * Compiles an intent down to flows. | 417 | * Compiles an intent down to flows. |
| 232 | * | 418 | * |
| ... | @@ -263,7 +449,10 @@ public class PathCompiler<T> { | ... | @@ -263,7 +449,10 @@ public class PathCompiler<T> { |
| 263 | switch (type) { | 449 | switch (type) { |
| 264 | case VLAN: | 450 | case VLAN: |
| 265 | manageVlanEncap(creator, flows, devices, intent); | 451 | manageVlanEncap(creator, flows, devices, intent); |
| 266 | - // TODO: implement MPLS case here | 452 | + break; |
| 453 | + case MPLS: | ||
| 454 | + manageMplsEncap(creator, flows, devices, intent); | ||
| 455 | + break; | ||
| 267 | default: | 456 | default: |
| 268 | // Nothing to do | 457 | // Nothing to do |
| 269 | } | 458 | } | ... | ... |
| ... | @@ -15,10 +15,7 @@ | ... | @@ -15,10 +15,7 @@ |
| 15 | */ | 15 | */ |
| 16 | package org.onosproject.net.intent.impl.compiler; | 16 | package org.onosproject.net.intent.impl.compiler; |
| 17 | 17 | ||
| 18 | -import java.util.LinkedList; | 18 | +import com.google.common.collect.ImmutableList; |
| 19 | -import java.util.List; | ||
| 20 | -import java.util.Set; | ||
| 21 | - | ||
| 22 | import org.apache.felix.scr.annotations.Activate; | 19 | import org.apache.felix.scr.annotations.Activate; |
| 23 | import org.apache.felix.scr.annotations.Component; | 20 | import org.apache.felix.scr.annotations.Component; |
| 24 | import org.apache.felix.scr.annotations.Deactivate; | 21 | import org.apache.felix.scr.annotations.Deactivate; |
| ... | @@ -42,10 +39,13 @@ import org.onosproject.net.newresource.ResourceService; | ... | @@ -42,10 +39,13 @@ import org.onosproject.net.newresource.ResourceService; |
| 42 | import org.onosproject.net.resource.link.LinkResourceAllocations; | 39 | import org.onosproject.net.resource.link.LinkResourceAllocations; |
| 43 | import org.slf4j.Logger; | 40 | import org.slf4j.Logger; |
| 44 | 41 | ||
| 45 | -import com.google.common.collect.ImmutableList; | 42 | +import java.util.LinkedList; |
| 43 | +import java.util.List; | ||
| 44 | +import java.util.Set; | ||
| 46 | 45 | ||
| 47 | import static org.slf4j.LoggerFactory.getLogger; | 46 | import static org.slf4j.LoggerFactory.getLogger; |
| 48 | 47 | ||
| 48 | + | ||
| 49 | @Component(immediate = true) | 49 | @Component(immediate = true) |
| 50 | public class PathIntentCompiler | 50 | public class PathIntentCompiler |
| 51 | extends PathCompiler<FlowRule> | 51 | extends PathCompiler<FlowRule> |
| ... | @@ -84,6 +84,7 @@ public class PathIntentCompiler | ... | @@ -84,6 +84,7 @@ public class PathIntentCompiler |
| 84 | List<DeviceId> devices = new LinkedList<>(); | 84 | List<DeviceId> devices = new LinkedList<>(); |
| 85 | compile(this, intent, rules, devices); | 85 | compile(this, intent, rules, devices); |
| 86 | 86 | ||
| 87 | + | ||
| 87 | return ImmutableList.of(new FlowRuleIntent(appId, null, rules, intent.resources())); | 88 | return ImmutableList.of(new FlowRuleIntent(appId, null, rules, intent.resources())); |
| 88 | } | 89 | } |
| 89 | 90 | ||
| ... | @@ -97,12 +98,14 @@ public class PathIntentCompiler | ... | @@ -97,12 +98,14 @@ public class PathIntentCompiler |
| 97 | return resourceService; | 98 | return resourceService; |
| 98 | } | 99 | } |
| 99 | 100 | ||
| 101 | + | ||
| 100 | @Override | 102 | @Override |
| 101 | public void createFlow(TrafficSelector originalSelector, TrafficTreatment originalTreatment, | 103 | public void createFlow(TrafficSelector originalSelector, TrafficTreatment originalTreatment, |
| 102 | ConnectPoint ingress, ConnectPoint egress, | 104 | ConnectPoint ingress, ConnectPoint egress, |
| 103 | int priority, boolean applyTreatment, | 105 | int priority, boolean applyTreatment, |
| 104 | List<FlowRule> rules, | 106 | List<FlowRule> rules, |
| 105 | List<DeviceId> devices) { | 107 | List<DeviceId> devices) { |
| 108 | + | ||
| 106 | TrafficSelector selector = DefaultTrafficSelector.builder(originalSelector) | 109 | TrafficSelector selector = DefaultTrafficSelector.builder(originalSelector) |
| 107 | .matchInPort(ingress.port()) | 110 | .matchInPort(ingress.port()) |
| 108 | .build(); | 111 | .build(); |
| ... | @@ -123,5 +126,6 @@ public class PathIntentCompiler | ... | @@ -123,5 +126,6 @@ public class PathIntentCompiler |
| 123 | .fromApp(appId) | 126 | .fromApp(appId) |
| 124 | .makePermanent() | 127 | .makePermanent() |
| 125 | .build()); | 128 | .build()); |
| 129 | + | ||
| 126 | } | 130 | } |
| 127 | } | 131 | } | ... | ... |
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment