diff options
Diffstat (limited to 'ipsec/ipsec-impl/src/main/java/io/fd/hc2vpp/ipsec/read')
3 files changed, 439 insertions, 0 deletions
diff --git a/ipsec/ipsec-impl/src/main/java/io/fd/hc2vpp/ipsec/read/IpsecReaderFactory.java b/ipsec/ipsec-impl/src/main/java/io/fd/hc2vpp/ipsec/read/IpsecReaderFactory.java new file mode 100644 index 000000000..e148022e8 --- /dev/null +++ b/ipsec/ipsec-impl/src/main/java/io/fd/hc2vpp/ipsec/read/IpsecReaderFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.hc2vpp.ipsec.read; + +import com.google.common.collect.Sets; +import com.google.inject.Inject; +import io.fd.honeycomb.translate.impl.read.GenericInitListReader; +import io.fd.honeycomb.translate.impl.read.GenericReader; +import io.fd.honeycomb.translate.read.ReaderFactory; +import io.fd.honeycomb.translate.read.registry.ModifiableReaderRegistryBuilder; +import io.fd.vpp.jvpp.core.future.FutureJVppCore; +import javax.annotation.Nonnull; +import org.opendaylight.yang.gen.v1.http.fd.io.hc2vpp.yang.vpp.ipsec.rev181213.IpsecStateSpdAugmentation; +import org.opendaylight.yang.gen.v1.http.fd.io.hc2vpp.yang.vpp.ipsec.rev181213.IpsecStateSpdAugmentationBuilder; +import org.opendaylight.yang.gen.v1.http.fd.io.hc2vpp.yang.vpp.ipsec.rev181213.ipsec.state.Spd; +import org.opendaylight.yang.gen.v1.http.fd.io.hc2vpp.yang.vpp.ipsec.rev181213.ipsec.state.spd.SpdEntries; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ipsec.rev181214.IpsecState; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ipsec.rev181214.ipsec.sa.state.grouping.Sa; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Factory producing readers for IpSec plugin's data. + */ +public final class IpsecReaderFactory implements ReaderFactory { + + private static final InstanceIdentifier<IpsecState> IPSEC_STATE_ID = InstanceIdentifier.create(IpsecState.class); + private FutureJVppCore vppApi; + + @Inject + public IpsecReaderFactory(final FutureJVppCore vppApi) { + this.vppApi = vppApi; + } + + @Override + public void init(@Nonnull final ModifiableReaderRegistryBuilder registry) { + registry.subtreeAdd(Sets + .newHashSet(InstanceIdentifier.create(IpsecState.class).child(Sa.class), + InstanceIdentifier.create(IpsecState.class).augmentation(IpsecStateSpdAugmentation.class) + .child(Spd.class)), new GenericReader<>(IPSEC_STATE_ID, + new IpsecStateCustomizer(vppApi))); + registry.addStructuralReader(IPSEC_STATE_ID.augmentation(IpsecStateSpdAugmentation.class), + IpsecStateSpdAugmentationBuilder.class); + registry.subtreeAdd(Sets + .newHashSet(InstanceIdentifier.create(Spd.class).child(SpdEntries.class)), + new GenericInitListReader<>( + IPSEC_STATE_ID.augmentation(IpsecStateSpdAugmentation.class).child(Spd.class), + new IpsecStateSpdCustomizer(vppApi))); + } +} diff --git a/ipsec/ipsec-impl/src/main/java/io/fd/hc2vpp/ipsec/read/IpsecStateCustomizer.java b/ipsec/ipsec-impl/src/main/java/io/fd/hc2vpp/ipsec/read/IpsecStateCustomizer.java new file mode 100644 index 000000000..4755c7a82 --- /dev/null +++ b/ipsec/ipsec-impl/src/main/java/io/fd/hc2vpp/ipsec/read/IpsecStateCustomizer.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.hc2vpp.ipsec.read; + +import com.google.common.base.Optional; +import io.fd.hc2vpp.common.translate.util.FutureJVppCustomizer; +import io.fd.hc2vpp.common.translate.util.Ipv4Translator; +import io.fd.hc2vpp.common.translate.util.Ipv6Translator; +import io.fd.hc2vpp.common.translate.util.JvppReplyConsumer; +import io.fd.honeycomb.translate.read.ReadContext; +import io.fd.honeycomb.translate.read.ReadFailedException; +import io.fd.honeycomb.translate.spi.read.Initialized; +import io.fd.honeycomb.translate.spi.read.InitializingReaderCustomizer; +import io.fd.honeycomb.translate.util.read.cache.DumpCacheManager; +import io.fd.honeycomb.translate.util.read.cache.EntityDumpExecutor; +import io.fd.vpp.jvpp.core.dto.IpsecSaDetails; +import io.fd.vpp.jvpp.core.dto.IpsecSaDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.IpsecSaDump; +import io.fd.vpp.jvpp.core.future.FutureJVppCore; +import java.util.LinkedList; +import javax.annotation.Nonnull; +import org.opendaylight.yang.gen.v1.http.fd.io.hc2vpp.yang.vpp.ipsec.rev181213.IpsecStateSpdAugmentation; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ipsec.rev181214.IkeEncryptionAlgorithmT; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ipsec.rev181214.IkeIntegrityAlgorithmT; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ipsec.rev181214.IpsecState; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ipsec.rev181214.IpsecStateBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ipsec.rev181214.ipsec.sa.state.grouping.Sa; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ipsec.rev181214.ipsec.sa.state.grouping.SaBuilder; +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public class IpsecStateCustomizer extends FutureJVppCustomizer + implements JvppReplyConsumer, InitializingReaderCustomizer<IpsecState, IpsecStateBuilder>, Ipv4Translator, + Ipv6Translator { + + private final DumpCacheManager<IpsecSaDetailsReplyDump, Void> ipsecSaDetailsReplyDumpManager; + + public IpsecStateCustomizer(final FutureJVppCore vppApi) { + super(vppApi); + this.ipsecSaDetailsReplyDumpManager = + new DumpCacheManager.DumpCacheManagerBuilder<IpsecSaDetailsReplyDump, Void>() + .withExecutor(new IpsecStateCustomizer.IpsecStateSaDetailsDumpExecutor(vppApi)) + .acceptOnly(IpsecSaDetailsReplyDump.class) + .build(); + } + + @Nonnull + @Override + public Initialized<? extends DataObject> init(@Nonnull final InstanceIdentifier<IpsecState> id, + @Nonnull final IpsecState readValue, @Nonnull final ReadContext ctx) { + return Initialized.create(id, readValue); + } + + @Nonnull + @Override + public IpsecStateBuilder getBuilder(@Nonnull final InstanceIdentifier<IpsecState> id) { + return new IpsecStateBuilder(); + } + + @Override + public void readCurrentAttributes(@Nonnull final InstanceIdentifier<IpsecState> id, + @Nonnull final IpsecStateBuilder builder, + @Nonnull final ReadContext ctx) throws ReadFailedException { + final Optional<IpsecSaDetailsReplyDump> dumpSa = + ipsecSaDetailsReplyDumpManager.getDump(id, ctx.getModificationCache()); + + if (dumpSa.isPresent()) { + LinkedList<Sa> listSa = new LinkedList<>(); + IpsecSaDetailsReplyDump reply = dumpSa.get(); + for (IpsecSaDetails details : reply.ipsecSaDetails) { + SaBuilder saBuilder = new SaBuilder(); + saBuilder.setSpi(Integer.toUnsignedLong(details.spi)) + .setAntiReplayWindow(Long.valueOf(details.replayWindow).intValue()) + .setAuthenticationAlgorithm(IkeIntegrityAlgorithmT.forValue(details.integAlg)) + .setEncryptionAlgorithm(IkeEncryptionAlgorithmT.forValue(details.cryptoAlg)); + listSa.add(saBuilder.build()); + } + builder.setSa(listSa); + } + } + + @Override + public void merge(@Nonnull final Builder<? extends DataObject> parentBuilder, @Nonnull final IpsecState readValue) { + IpsecStateBuilder ipsecParentBuilder = (IpsecStateBuilder) parentBuilder; + ipsecParentBuilder.setHoldDown(readValue.getHoldDown()) + .setPolicy(readValue.getPolicy()) + .setProposal(readValue.getProposal()) + .setRedundancy(readValue.getRedundancy()) + .setSa(readValue.getSa()) + .addAugmentation(IpsecStateSpdAugmentation.class, + readValue.augmentation(IpsecStateSpdAugmentation.class)); + } + + static final class IpsecStateSaDetailsDumpExecutor + implements EntityDumpExecutor<IpsecSaDetailsReplyDump, Void>, JvppReplyConsumer { + + private final FutureJVppCore jvpp; + + IpsecStateSaDetailsDumpExecutor(final FutureJVppCore jvpp) { + this.jvpp = jvpp; + } + + @Nonnull + @Override + public IpsecSaDetailsReplyDump executeDump(final InstanceIdentifier<?> identifier, final Void params) + throws ReadFailedException { + IpsecSaDump dump = new IpsecSaDump(); + dump.saId = -1; + return getReplyForRead(jvpp.ipsecSaDump(dump).toCompletableFuture(), identifier); + } + } +} diff --git a/ipsec/ipsec-impl/src/main/java/io/fd/hc2vpp/ipsec/read/IpsecStateSpdCustomizer.java b/ipsec/ipsec-impl/src/main/java/io/fd/hc2vpp/ipsec/read/IpsecStateSpdCustomizer.java new file mode 100644 index 000000000..45f54cdb8 --- /dev/null +++ b/ipsec/ipsec-impl/src/main/java/io/fd/hc2vpp/ipsec/read/IpsecStateSpdCustomizer.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.hc2vpp.ipsec.read; + +import com.google.common.base.Optional; +import io.fd.hc2vpp.common.translate.util.ByteDataTranslator; +import io.fd.hc2vpp.common.translate.util.FutureJVppCustomizer; +import io.fd.hc2vpp.common.translate.util.Ipv4Translator; +import io.fd.hc2vpp.common.translate.util.Ipv6Translator; +import io.fd.hc2vpp.common.translate.util.JvppReplyConsumer; +import io.fd.honeycomb.translate.read.ReadContext; +import io.fd.honeycomb.translate.read.ReadFailedException; +import io.fd.honeycomb.translate.spi.read.Initialized; +import io.fd.honeycomb.translate.spi.read.InitializingListReaderCustomizer; +import io.fd.honeycomb.translate.util.read.cache.DumpCacheManager; +import io.fd.honeycomb.translate.util.read.cache.EntityDumpExecutor; +import io.fd.vpp.jvpp.core.dto.IpsecSpdDetails; +import io.fd.vpp.jvpp.core.dto.IpsecSpdDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.IpsecSpdDump; +import io.fd.vpp.jvpp.core.dto.IpsecSpdsDetails; +import io.fd.vpp.jvpp.core.dto.IpsecSpdsDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.IpsecSpdsDump; +import io.fd.vpp.jvpp.core.future.FutureJVppCore; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.opendaylight.yang.gen.v1.http.fd.io.hc2vpp.yang.vpp.ipsec.rev181213.IpsecStateSpdAugmentationBuilder; +import org.opendaylight.yang.gen.v1.http.fd.io.hc2vpp.yang.vpp.ipsec.rev181213.ipsec.state.Spd; +import org.opendaylight.yang.gen.v1.http.fd.io.hc2vpp.yang.vpp.ipsec.rev181213.ipsec.state.SpdBuilder; +import org.opendaylight.yang.gen.v1.http.fd.io.hc2vpp.yang.vpp.ipsec.rev181213.ipsec.state.SpdKey; +import org.opendaylight.yang.gen.v1.http.fd.io.hc2vpp.yang.vpp.ipsec.rev181213.ipsec.state.spd.SpdEntries; +import org.opendaylight.yang.gen.v1.http.fd.io.hc2vpp.yang.vpp.ipsec.rev181213.ipsec.state.spd.SpdEntriesBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddressBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddressNoZone; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ipsec.rev181214.IpsecSpdOperation; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ipsec.rev181214.IpsecTrafficDirection; +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class IpsecStateSpdCustomizer extends FutureJVppCustomizer + implements JvppReplyConsumer, InitializingListReaderCustomizer<Spd, SpdKey, SpdBuilder>, Ipv4Translator, + Ipv6Translator { + + private static final Logger LOG = LoggerFactory.getLogger(IpsecStateSpdCustomizer.class); + private final DumpCacheManager<IpsecSpdDetailsReplyDump, Void> ipsecSpdDetailsReplyDumpManager; + private final DumpCacheManager<IpsecSpdsDetailsReplyDump, Void> ipsecSpdsReplyDumpManager; + + public IpsecStateSpdCustomizer(final FutureJVppCore vppApi) { + super(vppApi); + IpsecStateSpdsReplyDumpExecutor spdsExecutor = + new IpsecStateSpdCustomizer.IpsecStateSpdsReplyDumpExecutor(vppApi); + this.ipsecSpdsReplyDumpManager = new DumpCacheManager.DumpCacheManagerBuilder<IpsecSpdsDetailsReplyDump, Void>() + .withExecutor(spdsExecutor) + .acceptOnly(IpsecSpdsDetailsReplyDump.class) + .build(); + + this.ipsecSpdDetailsReplyDumpManager = + new DumpCacheManager.DumpCacheManagerBuilder<IpsecSpdDetailsReplyDump, Void>() + .withExecutor( + new IpsecStateSpdCustomizer.IpsecStateSpdDetailsDumpExecutor(vppApi, spdsExecutor)) + .acceptOnly(IpsecSpdDetailsReplyDump.class) + .build(); + } + + @Nonnull + @Override + public Initialized<? extends DataObject> init(@Nonnull final InstanceIdentifier<Spd> id, + @Nonnull final Spd readValue, + @Nonnull final ReadContext ctx) { + return Initialized.create(id, readValue); + } + + @Nonnull + @Override + public SpdBuilder getBuilder(@Nonnull final InstanceIdentifier<Spd> id) { + return new SpdBuilder(); + } + + @Override + public void readCurrentAttributes(@Nonnull final InstanceIdentifier<Spd> id, @Nonnull final SpdBuilder builder, + @Nonnull final ReadContext ctx) throws ReadFailedException { + SpdKey key = id.firstKeyOf(Spd.class); + builder.withKey(key); + builder.setSpdId(key.getSpdId()); + Optional<IpsecSpdDetailsReplyDump> spdDump = + ipsecSpdDetailsReplyDumpManager.getDump(id, ctx.getModificationCache()); + if (spdDump.isPresent()) { + List<SpdEntries> spdEntries = + spdDump.get().ipsecSpdDetails.stream().map(details -> translateDetailToEntry(details)) + .collect(Collectors.toList()); + builder.setSpdEntries(spdEntries); + } + } + + @Nonnull + @Override + public List<SpdKey> getAllIds(@Nonnull final InstanceIdentifier<Spd> id, @Nonnull final ReadContext context) + throws ReadFailedException { + List<SpdKey> spdKeys = new LinkedList<>(); + Optional<IpsecSpdsDetailsReplyDump> spdsDump = + ipsecSpdsReplyDumpManager.getDump(id, context.getModificationCache()); + if (spdsDump.isPresent()) { + spdKeys = spdsDump.get().ipsecSpdsDetails.stream().map(details -> new SpdKey(details.spdId)) + .collect(Collectors.toList()); + } + + LOG.debug("SPDs found in VPP: {}", spdKeys); + return spdKeys; + } + + @Override + public void merge(@Nonnull final Builder<? extends DataObject> builder, @Nonnull final List<Spd> readData) { + ((IpsecStateSpdAugmentationBuilder) builder).setSpd(readData); + } + + private SpdEntries translateDetailToEntry(final IpsecSpdDetails details) { + + SpdEntriesBuilder builder = new SpdEntriesBuilder(); + builder.setDirection(IpsecTrafficDirection.forValue(details.isOutbound)) + .setIsIpv6(ByteDataTranslator.INSTANCE.byteToBoolean(details.isIpv6)) + .setPriority(details.priority); + switch (details.policy) { + case 0: + builder.setOperation(IpsecSpdOperation.Bypass); + break; + case 1: + builder.setOperation(IpsecSpdOperation.Discard); + break; + case 3: + builder.setOperation(IpsecSpdOperation.Protect); + builder.setProtectSaId(details.saId); + break; + } + + if (builder.isIsIpv6()) { + processIpv6AddressRanges(builder, details); + } else { + processIpv4AddressRanges(builder, details); + } + + return builder.build(); + } + + private void processIpv4AddressRanges(final SpdEntriesBuilder builder, final IpsecSpdDetails details) { + if (details.localStartAddr != null && details.localStartAddr.length > 0) { + builder.setLaddrStart(IpAddressBuilder.getDefaultInstance( + new IpAddressNoZone(arrayToIpv4AddressNoZone(details.localStartAddr)).stringValue())); + } + if (details.localStopAddr != null && details.localStopAddr.length > 0) { + builder.setLaddrStop(IpAddressBuilder.getDefaultInstance( + new IpAddressNoZone(arrayToIpv4AddressNoZone(details.localStopAddr)).stringValue())); + } + if (details.remoteStartAddr != null && details.remoteStartAddr.length > 0) { + builder.setRaddrStart(IpAddressBuilder.getDefaultInstance( + new IpAddressNoZone(arrayToIpv4AddressNoZone(details.remoteStartAddr)).stringValue())); + } + if (details.remoteStopAddr != null && details.remoteStopAddr.length > 0) { + builder.setRaddrStop(IpAddressBuilder.getDefaultInstance( + new IpAddressNoZone(arrayToIpv4AddressNoZone(details.remoteStopAddr)).stringValue())); + } + } + + private void processIpv6AddressRanges(final SpdEntriesBuilder builder, final IpsecSpdDetails details) { + if (details.localStartAddr != null && details.localStartAddr.length > 0) { + builder.setLaddrStart(IpAddressBuilder.getDefaultInstance( + new IpAddressNoZone(arrayToIpv6AddressNoZone(details.localStartAddr)).stringValue())); + } + if (details.localStopAddr != null && details.localStopAddr.length > 0) { + builder.setLaddrStop(IpAddressBuilder.getDefaultInstance( + new IpAddressNoZone(arrayToIpv6AddressNoZone(details.localStopAddr)).stringValue())); + } + if (details.remoteStartAddr != null && details.remoteStartAddr.length > 0) { + builder.setRaddrStart(IpAddressBuilder.getDefaultInstance( + new IpAddressNoZone(arrayToIpv6AddressNoZone(details.remoteStartAddr)).stringValue())); + } + if (details.remoteStopAddr != null && details.remoteStopAddr.length > 0) { + builder.setRaddrStop(IpAddressBuilder.getDefaultInstance( + new IpAddressNoZone(arrayToIpv6AddressNoZone(details.remoteStopAddr)).stringValue())); + } + } + + public static class IpsecStateSpdDetailsDumpExecutor + implements EntityDumpExecutor<IpsecSpdDetailsReplyDump, Void>, JvppReplyConsumer { + private FutureJVppCore jvpp; + private IpsecStateSpdsReplyDumpExecutor spdsDumpExecutor; + + public IpsecStateSpdDetailsDumpExecutor( + final FutureJVppCore vppApi, final IpsecStateSpdsReplyDumpExecutor spdsDumpExecutor) { + this.jvpp = vppApi; + this.spdsDumpExecutor = spdsDumpExecutor; + } + + @Nonnull + @Override + public IpsecSpdDetailsReplyDump executeDump(final InstanceIdentifier<?> identifier, final Void params) + throws ReadFailedException { + IpsecSpdDetailsReplyDump fullReplyDump = new IpsecSpdDetailsReplyDump(); + fullReplyDump.ipsecSpdDetails = new LinkedList<>(); + + Optional<IpsecSpdsDetailsReplyDump> spdsReply = + Optional.of(spdsDumpExecutor.executeDump(identifier, params)); + IpsecSpdsDetailsReplyDump spdDump = spdsReply.get(); + for (IpsecSpdsDetails spdsDetail : spdDump.ipsecSpdsDetails) { + IpsecSpdDump dump = new IpsecSpdDump(); + dump.spdId = spdsDetail.spdId; + dump.saId = -1; + IpsecSpdDetailsReplyDump reply = + getReplyForRead(jvpp.ipsecSpdDump(dump).toCompletableFuture(), identifier); + fullReplyDump.ipsecSpdDetails.addAll(reply.ipsecSpdDetails); + } + + return fullReplyDump; + } + } + + private class IpsecStateSpdsReplyDumpExecutor implements EntityDumpExecutor<IpsecSpdsDetailsReplyDump, Void> { + private final FutureJVppCore jvpp; + + public IpsecStateSpdsReplyDumpExecutor( + final FutureJVppCore vppApi) { + this.jvpp = vppApi; + } + + @Nonnull + @Override + public IpsecSpdsDetailsReplyDump executeDump(final InstanceIdentifier<?> identifier, final Void params) + throws ReadFailedException { + return getReplyForRead(jvpp.ipsecSpdsDump(new IpsecSpdsDump()).toCompletableFuture(), identifier); + } + } +} |