diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e86cde2..b1f4be4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -140,7 +140,7 @@ sonarqube: - if: $CI_COMMIT_BRANCH == "next" control/run-on-staging: - image: node:22-alpine + image: node:23-alpine stage: qa script: - ./scripts/gitlab-deploy-check diff --git a/build.gradle b/build.gradle index 5293ce7..a539cb6 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'org.springframework.boot' version "3.2.4" id 'distribution' id 'jacoco' - id "com.google.cloud.tools.jib" version "3.4.3" + id "com.google.cloud.tools.jib" version "3.4.4" id "com.google.osdetector" version "1.7.3" } @@ -44,7 +44,7 @@ dependencies { implementation "org.thymeleaf:thymeleaf:3.1.2.RELEASE" implementation "org.thymeleaf:thymeleaf-spring6:3.1.2.RELEASE" - implementation platform('io.sentry:sentry-bom:7.15.0') + implementation platform('io.sentry:sentry-bom:7.16.0') implementation 'io.sentry:sentry-spring-boot-starter' implementation 'io.sentry:sentry-logback' @@ -72,7 +72,7 @@ dependencies { exclude group: 'org.hamcrest', module: 'hamcrest-core' } - testImplementation "org.wiremock:wiremock-jetty12:3.9.1" + testImplementation "org.wiremock:wiremock-jetty12:3.9.2" testImplementation 'net.jqwik:jqwik:1.9.1' testImplementation "net.ripe.rpki:rpki-commons:$rpki_commons_version:tests" testImplementation 'org.assertj:assertj-core' diff --git a/src/main/java/net/ripe/rpki/rest/service/AnnouncementService.java b/src/main/java/net/ripe/rpki/rest/service/AnnouncementService.java index ff385e8..0ff0856 100644 --- a/src/main/java/net/ripe/rpki/rest/service/AnnouncementService.java +++ b/src/main/java/net/ripe/rpki/rest/service/AnnouncementService.java @@ -9,10 +9,8 @@ import net.ripe.ipresource.IpResource; import net.ripe.ipresource.ImmutableResourceSet; import net.ripe.ipresource.etree.NestedIntervalMap; -import net.ripe.rpki.commons.validation.roa.AllowedRoute; import net.ripe.rpki.commons.validation.roa.AnnouncedRoute; import net.ripe.rpki.commons.validation.roa.RouteOriginValidationPolicy; -import net.ripe.rpki.commons.validation.roa.RouteValidityState; import net.ripe.rpki.rest.exception.BadRequestException; import net.ripe.rpki.rest.pojo.BgpAnnouncement; import net.ripe.rpki.rest.pojo.ApiRoaPrefix; @@ -35,10 +33,8 @@ import org.springframework.web.bind.annotation.RestController; import java.time.Instant; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -160,35 +156,10 @@ public ResponseEntity> getAffectedAnnouncementsForCaAndRoa final RoaConfigurationData roaConfiguration = roaViewService.getRoaConfiguration(ca.getId()); final Set ignoredAnnouncements = Utils.getIgnoredAnnouncements(roaAlertConfigurationViewService, ca.getId()); - final Set routesValidatedByOthers = new HashSet<>(); - final NestedIntervalMap> currentRouteMap = allowedRoutesToNestedIntervalMap(roaConfiguration.getPrefixes()); - Stream.of(true, false) - .filter(announcements::containsKey) - .flatMap(verifiedOrNot -> announcements.get(verifiedOrNot).stream()) - .map(BgpRisEntry::toAnnouncedRoute) - .forEach(announcedRoute -> { - final RouteValidityState currentValidityState = RouteOriginValidationPolicy.validateAnnouncedRoute(currentRouteMap, announcedRoute); - if (currentValidityState == RouteValidityState.VALID && - !(roaAsn.equals(announcedRoute.getOriginAsn()) && roaPrefix.equals(announcedRoute.getPrefix()))) { - routesValidatedByOthers.add(announcedRoute); - } - }); - - final List bgpAnnouncements = Utils.makeBgpAnnouncementList(announcements, Collections.singletonList( - new AllowedRoute(roaAsn, roaPrefix, roa.getMaxLength())), - ignoredAnnouncements); - - final List knownAnnouncements = new ArrayList<>(); - for (BgpAnnouncement announcement : bgpAnnouncements) { - final AnnouncedRoute announcedRoute = new AnnouncedRoute(Asn.parse(announcement.getAsn()), IpRange.parse(announcement.getPrefix())); - if (announcement.getCurrentState() == RouteValidityState.VALID || - ((announcement.getCurrentState() == RouteValidityState.INVALID_ASN || - announcement.getCurrentState() == RouteValidityState.INVALID_LENGTH) && - !routesValidatedByOthers.contains(announcedRoute))) { - knownAnnouncements.add(announcement); - } - } - return ok(knownAnnouncements); + final List affectedAnnouncements = Utils.getAffectedAnnouncements( + roaConfiguration, announcements, ignoredAnnouncements, roaAsn, roaPrefix, roa.getMaxLength()); + + return ok(affectedAnnouncements); } public static final String NO_CA_RESOURCES = "no-ca-resources"; diff --git a/src/main/java/net/ripe/rpki/rest/service/CaRoaConfigurationService.java b/src/main/java/net/ripe/rpki/rest/service/CaRoaConfigurationService.java index 608cb95..745bf25 100644 --- a/src/main/java/net/ripe/rpki/rest/service/CaRoaConfigurationService.java +++ b/src/main/java/net/ripe/rpki/rest/service/CaRoaConfigurationService.java @@ -3,12 +3,10 @@ import com.google.common.base.Preconditions; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.Value; import lombok.extern.slf4j.Slf4j; import net.ripe.ipresource.*; import net.ripe.ipresource.etree.NestedIntervalMap; import net.ripe.rpki.commons.validation.roa.*; -import net.ripe.rpki.domain.roa.RoaConfigurationRepository; import net.ripe.rpki.rest.pojo.BgpAnnouncement; import net.ripe.rpki.rest.pojo.BgpAnnouncementChange; import net.ripe.rpki.rest.pojo.PublishSet; @@ -53,7 +51,6 @@ public class CaRoaConfigurationService extends AbstractCaRestService { @Autowired public CaRoaConfigurationService(RoaViewService roaViewService, - RoaConfigurationRepository roaConfigurationRepository, BgpRisEntryViewService bgpRisEntryViewService, RoaAlertConfigurationViewService roaAlertConfigurationViewService, CommandService commandService) { @@ -163,10 +160,10 @@ static RouteValidityState determineValidityState(IpRange announcedPrefix, String @PostMapping(path = "stage") @Operation(summary = "Stage ROA changes for the given CA") public ResponseEntity stageRoaChanges(@PathVariable("caName") final CaName caName, - @RequestBody final List futureRoas) { + @RequestBody final List futureRoasPrefixes) { log.info("REST call: Stage ROAs for CA: {}", caName); - final Optional errorMessage = Utils.errorsInUserInputRoas(futureRoas); + final Optional errorMessage = Utils.errorsInUserInputRoas(futureRoasPrefixes); if (errorMessage.isPresent()) { return ResponseEntity.status(BAD_REQUEST).body(of(ERROR, "New ROAs are not correct: " + errorMessage.get())); } @@ -177,80 +174,71 @@ public ResponseEntity stageRoaChanges(@PathVariable("caName") final CaName ca final Map> bgpAnnouncements = bgpRisEntryViewService.findMostSpecificContainedAndNotContained(certifiedResources); final RoaConfigurationData currentRoaConfiguration = roaViewService.getRoaConfiguration(ca.getId()); - final Set currentRoutes = new HashSet<>(currentRoaConfiguration.getPrefixes()); + final Set currentRoas = new HashSet<>(currentRoaConfiguration.getPrefixes()); // MAY throw, but IllegalArgumentExceptions are translated to HTTP 500 through @ControllerAdvice + List futureRoas = futureRoasPrefixes.stream() + .map(r -> new AllowedRoute(Asn.parse(r.getAsn()), IpRange.parse(r.getPrefix()), r.getMaxLength())) + .toList(); - var effectOfFutureRoas = buildAffectedRanges(futureRoas, currentRoutes.stream().map(RoaPrefixData::toAllowedRoute).collect(Collectors.toSet())); + var affectedRanges = buildAffectedRanges( + futureRoas, + currentRoas.stream() + .map(RoaPrefixData::toAllowedRoute) + .collect(Collectors.toSet())); - Optional validationError = Roas.validateRoaUpdate(effectOfFutureRoas.futureRoutes); + Optional validationError = Roas.validateRoaUpdate(futureRoas); if (validationError.isPresent()) { return ResponseEntity.status(BAD_REQUEST).body(of(ERROR, validationError.get())); } final List bgpAnnouncementChanges = getBgpAnnouncementChanges( - roaAlertConfigurationViewService, - ca, currentRoutes, effectOfFutureRoas.futureRoutes, bgpAnnouncements, effectOfFutureRoas.affectedRanges); + currentRoas, futureRoas, bgpAnnouncements, affectedRanges, + getIgnoredAnnouncement(roaAlertConfigurationViewService, ca.getId())); return ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) .eTag(currentRoaConfiguration.entityTag()) .body(bgpAnnouncementChanges); } - - /** - * Gather the set of resources span by the futureRoas and add an AllowedRoute to futureRoutes for all - * ROAs in futureRoas that are not in currentRoutes. - */ - private static AffectedRangesForVRPs buildAffectedRanges(List futureRoas, Set currentRoutes) { - var affectedRanges = new IpResourceSet(); + + static Set buildAffectedRanges(List futureRoas, Set currentRoas) { + var affectedRanges = new HashSet(); var futureRoutes = new HashSet(); - for (ApiRoaPrefix roa : futureRoas) { - final IpRange roaIpRange = IpRange.parse(roa.getPrefix()); - final AllowedRoute route = new AllowedRoute(Asn.parse(roa.getAsn()), roaIpRange, roa.getMaxLength()); - futureRoutes.add(route); - if (!currentRoutes.contains(route)) - affectedRanges.add(roaIpRange); + for (var roa : futureRoas) { + futureRoutes.add(roa); + if (!currentRoas.contains(roa)) + affectedRanges.add(roa.getPrefix()); } - for (var route : currentRoutes) { - if (!futureRoutes.contains(route)) - affectedRanges.add(route.getPrefix()); + for (var roa : currentRoas) { + if (!futureRoutes.contains(roa)) + affectedRanges.add(roa.getPrefix()); } - - return new AffectedRangesForVRPs(affectedRanges, futureRoutes); + return affectedRanges; } - @Value - private static class AffectedRangesForVRPs { - private final IpResourceSet affectedRanges; - private final Set futureRoutes; - } + static List getBgpAnnouncementChanges( + Collection currentRoutes, + Collection futureRoutes, + Map> bgpAnnouncements, + Set affectedRanges, + Set ignoredAnnouncements) { - private static List getBgpAnnouncementChanges( - RoaAlertConfigurationViewService service, - HostedCertificateAuthorityData ca, - Set currentRoutes, - Set futureRoutes, - Map> bgpAnnouncements, - IpResourceSet affectedRanges) { final NestedIntervalMap> currentRouteMap = allowedRoutesToNestedIntervalMap(currentRoutes); final NestedIntervalMap> futureRouteMap = allowedRoutesToNestedIntervalMap(futureRoutes); - final Set ignoredAnnouncements = getIgnoredAnnouncement(service, ca.getId()); final List result = new ArrayList<>(); for (Boolean verifiedOrNot : new Boolean[]{true, false}) { - if (bgpAnnouncements.containsKey(verifiedOrNot)) { - for (BgpRisEntry bgp : bgpAnnouncements.get(verifiedOrNot)) { - final AnnouncedRoute announcedRoute = bgp.toAnnouncedRoute(); - final RouteValidityState currentValidityState = RouteOriginValidationPolicy.validateAnnouncedRoute(currentRouteMap, announcedRoute); - final RouteValidityState futureValidityState = RouteOriginValidationPolicy.validateAnnouncedRoute(futureRouteMap, announcedRoute); - final boolean isSuppressed = ignoredAnnouncements.contains(announcedRoute); - result.add(new BgpAnnouncementChange(bgp.getOrigin().toString(), bgp.getPrefix().toString(), - bgp.getVisibility(), isSuppressed, currentValidityState, futureValidityState, - affectedRanges.contains(bgp.getPrefix()), - verifiedOrNot)); - } + for (BgpRisEntry bgp : bgpAnnouncements.getOrDefault(verifiedOrNot, Collections.emptyList())) { + AnnouncedRoute announcedRoute = bgp.toAnnouncedRoute(); + RouteValidityState currentValidityState = RouteOriginValidationPolicy.validateAnnouncedRoute(currentRouteMap, announcedRoute); + RouteValidityState futureValidityState = RouteOriginValidationPolicy.validateAnnouncedRoute(futureRouteMap, announcedRoute); + boolean isSuppressed = ignoredAnnouncements.contains(announcedRoute); + boolean affectedByChange = affectedRanges.stream().anyMatch(r -> r.contains(bgp.getPrefix())); + result.add(new BgpAnnouncementChange(bgp.getOrigin().toString(), bgp.getPrefix().toString(), + bgp.getVisibility(), isSuppressed, currentValidityState, futureValidityState, + affectedByChange, verifiedOrNot)); } } return result; diff --git a/src/main/java/net/ripe/rpki/rest/service/Roas.java b/src/main/java/net/ripe/rpki/rest/service/Roas.java index 5bd2ba3..d76996b 100644 --- a/src/main/java/net/ripe/rpki/rest/service/Roas.java +++ b/src/main/java/net/ripe/rpki/rest/service/Roas.java @@ -49,7 +49,7 @@ public static Set applyDiff(Set currentRoas, RoaDiff return futureRoas; } - public static Optional validateRoaUpdate(Set futureRoutes) { + public static Optional validateRoaUpdate(Collection futureRoutes) { var futureMap = futureRoutes.stream().collect(Collectors.toMap( r -> new AnnouncedRoute(r.getAsn(), r.getPrefix()), r -> Collections.singletonList(r.getMaximumLength()), diff --git a/src/main/java/net/ripe/rpki/rest/service/Utils.java b/src/main/java/net/ripe/rpki/rest/service/Utils.java index b3d0b89..c30ea90 100644 --- a/src/main/java/net/ripe/rpki/rest/service/Utils.java +++ b/src/main/java/net/ripe/rpki/rest/service/Utils.java @@ -1,50 +1,52 @@ package net.ripe.rpki.rest.service; import com.google.common.annotations.VisibleForTesting; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; import lombok.NonNull; +import lombok.experimental.UtilityClass; import net.ripe.ipresource.Asn; +import net.ripe.ipresource.ImmutableResourceSet; import net.ripe.ipresource.IpRange; import net.ripe.ipresource.IpResource; -import net.ripe.ipresource.ImmutableResourceSet; import net.ripe.ipresource.etree.NestedIntervalMap; import net.ripe.rpki.commons.validation.roa.*; -import net.ripe.rpki.rest.pojo.BgpAnnouncement; import net.ripe.rpki.rest.pojo.ApiRoaPrefix; +import net.ripe.rpki.rest.pojo.BgpAnnouncement; import net.ripe.rpki.server.api.dto.BgpRisEntry; import net.ripe.rpki.server.api.dto.RoaAlertConfigurationData; +import net.ripe.rpki.server.api.dto.RoaConfigurationData; +import net.ripe.rpki.server.api.dto.RoaConfigurationPrefixData; import net.ripe.rpki.server.api.services.read.RoaAlertConfigurationViewService; import org.springframework.http.ResponseEntity; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.Stream; import static net.ripe.rpki.commons.validation.roa.RouteOriginValidationPolicy.allowedRoutesToNestedIntervalMap; -@NoArgsConstructor(access = AccessLevel.PRIVATE) +@UtilityClass class Utils { static List makeBgpAnnouncementList(Map> announcements, - Iterable currentAllowedRoutes, - Set ignoredAnnouncements) { + Iterable currentAllowedRoutes, + Set ignoredAnnouncements) { + final NestedIntervalMap> currentRouteMap = allowedRoutesToNestedIntervalMap(currentAllowedRoutes); // Verified announcements first, then the rest. return Stream.of(true, false) - .flatMap(verifiedOrNot -> announcements.getOrDefault(verifiedOrNot, Collections.emptyList()).stream() - .map(announcement -> { - // Create BgbAnnouncement, this needs the BgpRisEntry as well as the derived AnnouncedRoute - final AnnouncedRoute announcedRoute = announcement.toAnnouncedRoute(); - - final RouteValidityState currentValidityState = RouteOriginValidationPolicy.validateAnnouncedRoute(currentRouteMap, announcedRoute); - final boolean isSuppressed = ignoredAnnouncements.contains(announcedRoute); - return new BgpAnnouncement( - announcement.getOrigin().toString(), announcement.getPrefix().toString(), - announcement.getVisibility(), currentValidityState, - isSuppressed, verifiedOrNot); - })) - .toList(); + .flatMap(verifiedOrNot -> announcements.getOrDefault(verifiedOrNot, Collections.emptyList()) + .stream() + .map(announcement -> { + // Create BgbAnnouncement, this needs the BgpRisEntry as well as the derived AnnouncedRoute + final AnnouncedRoute announcedRoute = announcement.toAnnouncedRoute(); + + final RouteValidityState currentValidityState = RouteOriginValidationPolicy.validateAnnouncedRoute(currentRouteMap, announcedRoute); + final boolean isSuppressed = ignoredAnnouncements.contains(announcedRoute); + return new BgpAnnouncement( + announcement.getOrigin().toString(), announcement.getPrefix().toString(), + announcement.getVisibility(), currentValidityState, + isSuppressed, verifiedOrNot); + })) + .toList(); } static Set getIgnoredAnnouncements(RoaAlertConfigurationViewService roaAlertConfigurationViewService, long caId) { @@ -141,12 +143,47 @@ public static void cleanupOnError(Runnable runnable, Runnable onError) { } @NonNull - protected static ResponseEntity badRequestError(Exception e) { + static ResponseEntity badRequestError(Exception e) { return badRequestError(e.getMessage()); } @NonNull - protected static ResponseEntity badRequestError(String errorMessage) { + static ResponseEntity badRequestError(String errorMessage) { return ResponseEntity.badRequest().body(Map.of("error", errorMessage)); } + + static List getAffectedAnnouncements(RoaConfigurationData roaConfiguration, + Map> announcements, + Set ignoredAnnouncements, + Asn roaAsn, IpRange roaPrefix, Integer roaMaxLength) { + + final Set routesValidatedByOthers = new HashSet<>(); + final NestedIntervalMap> currentRouteMap = allowedRoutesToNestedIntervalMap(roaConfiguration.getPrefixes()); + Stream.of(true, false) + .flatMap(verifiedOrNot -> announcements.getOrDefault(verifiedOrNot, Collections.emptyList()).stream()) + .map(BgpRisEntry::toAnnouncedRoute) + .forEach(announcedRoute -> { + final RouteValidityState currentValidityState = RouteOriginValidationPolicy.validateAnnouncedRoute(currentRouteMap, announcedRoute); + if (currentValidityState == RouteValidityState.VALID && + !(roaAsn.equals(announcedRoute.getOriginAsn()) && roaPrefix.equals(announcedRoute.getPrefix()))) { + routesValidatedByOthers.add(announcedRoute); + } + }); + + final List bgpAnnouncements = makeBgpAnnouncementList(announcements, Collections.singletonList( + new AllowedRoute(roaAsn, roaPrefix, roaMaxLength)), + ignoredAnnouncements); + + final List knownAnnouncements = new ArrayList<>(); + for (BgpAnnouncement announcement : bgpAnnouncements) { + final AnnouncedRoute announcedRoute = new AnnouncedRoute(Asn.parse(announcement.getAsn()), IpRange.parse(announcement.getPrefix())); + if (announcement.getCurrentState() == RouteValidityState.VALID || + ((announcement.getCurrentState() == RouteValidityState.INVALID_ASN || + announcement.getCurrentState() == RouteValidityState.INVALID_LENGTH) && + !routesValidatedByOthers.contains(announcedRoute))) { + knownAnnouncements.add(announcement); + } + } + return knownAnnouncements; + } } diff --git a/src/test/java/net/ripe/rpki/rest/service/AnnouncementServiceTest.java b/src/test/java/net/ripe/rpki/rest/service/AnnouncementServiceTest.java index 4dc580e..89dd81c 100644 --- a/src/test/java/net/ripe/rpki/rest/service/AnnouncementServiceTest.java +++ b/src/test/java/net/ripe/rpki/rest/service/AnnouncementServiceTest.java @@ -358,7 +358,6 @@ public void affected_shouldReturnAnnouncementAffectedByROAValidatedByOtherROAs() .andExpect(jsonPath("$.[1].currentState").value("INVALID_ASN")); } - @Test public void affected_shouldNotReturnUnknownAnnouncements() throws Exception { @@ -368,8 +367,8 @@ public void affected_shouldNotReturnUnknownAnnouncements() throws Exception { final BgpRisEntry e1 = new BgpRisEntry(new Asn(10), IpRange.parse("191.168.0.0/16"), 10); final BgpRisEntry e2 = new BgpRisEntry(new Asn(10), IpRange.parse("192.168.0.0/16"), 10); Map> bgpRisEntries = new HashMap<>(); - bgpRisEntries.put(true, Collections.singletonList(e2)); bgpRisEntries.put(false, Collections.singletonList(e1)); + bgpRisEntries.put(true, Collections.singletonList(e2)); when(bgpRisEntryViewService.findMostSpecificContainedAndNotContained(ipResourceSet)).thenReturn(bgpRisEntries); when(roaService.getRoaConfiguration(CA_ID)).thenReturn(new RoaConfigurationData(new ArrayList<>())); diff --git a/src/test/java/net/ripe/rpki/rest/service/CaRoaConfigurationServiceTest.java b/src/test/java/net/ripe/rpki/rest/service/CaRoaConfigurationServiceTest.java index a8c88ad..a42cfbf 100644 --- a/src/test/java/net/ripe/rpki/rest/service/CaRoaConfigurationServiceTest.java +++ b/src/test/java/net/ripe/rpki/rest/service/CaRoaConfigurationServiceTest.java @@ -5,6 +5,7 @@ import net.ripe.rpki.commons.util.VersionedId; import net.ripe.rpki.commons.validation.roa.AllowedRoute; import net.ripe.rpki.commons.validation.roa.RouteValidityState; +import net.ripe.rpki.rest.pojo.BgpAnnouncementChange; import net.ripe.rpki.server.api.commands.UpdateRoaConfigurationCommand; import net.ripe.rpki.server.api.dto.BgpRisEntry; import net.ripe.rpki.server.api.dto.HostedCertificateAuthorityData; @@ -737,4 +738,29 @@ public void testDetermineValidityState() { thenThrownBy(() -> CaRoaConfigurationService.determineValidityState(testNet1, "not_an_as", ALLOWED_ROUTE)).isInstanceOf(IllegalArgumentException.class); thenThrownBy(() -> CaRoaConfigurationService.determineValidityState(testNet1, "-1", ALLOWED_ROUTE)).isInstanceOf(IllegalArgumentException.class); } + + @Test + public void testBuildAffectedRanges() { + var as1136 = new Asn(1136); + var currentRoutes = new HashSet<>(List.of( + new AllowedRoute(as1136, IpRange.parse("31.161.128.0/17"), 17), + new AllowedRoute(as1136, IpRange.parse("31.161.0.0/16"), 16), + new AllowedRoute(as1136, IpRange.parse("31.161.0.0/17"), 17), + new AllowedRoute(as1136, IpRange.parse("31.160.128.0/17"), 17), + new AllowedRoute(as1136, IpRange.parse("31.160.0.0/17"), 18) + )); + + var futureRoas = List.of( + new AllowedRoute(as1136, IpRange.parse("31.161.128.0/17"), 17), + new AllowedRoute(as1136, IpRange.parse("31.161.0.0/17"), 17) + ); + var affectedRanges = CaRoaConfigurationService.buildAffectedRanges(futureRoas, currentRoutes); + assertThat(affectedRanges).isNotNull(); + + Map> bgpAnnouncements = Map.of(true, List.of(new BgpRisEntry(as1136, IpRange.parse("31.160.0.0/15"), 15))); + final List bgpAnnouncementChanges = CaRoaConfigurationService.getBgpAnnouncementChanges( + currentRoutes, new HashSet<>(futureRoas), bgpAnnouncements, affectedRanges, Collections.emptySet()); + + assertThat(bgpAnnouncementChanges.get(0).affectedByChange).isFalse(); + } }