diff --git a/build/docker/api/application.conf b/build/docker/api/application.conf index 95d4702a6..f3d2e8ef2 100644 --- a/build/docker/api/application.conf +++ b/build/docker/api/application.conf @@ -161,6 +161,17 @@ vinyldns { port=${?API_SERVICE_PORT} } + api { + limits { + batchchange-routing-max-items-limit = 100 + membership-routing-default-max-items = 100 + membership-routing-max-items-limit = 1000 + membership-routing-max-groups-list-limit = 3000 + recordset-routing-default-max-items= 100 + zone-routing-default-max-items = 100 + zone-routing-max-items-limit = 100 + } + } approved-name-servers = [ "172.17.42.1.", diff --git a/build/docker/portal/application.conf b/build/docker/portal/application.conf index e183dc8bd..e691be760 100644 --- a/build/docker/portal/application.conf +++ b/build/docker/portal/application.conf @@ -76,6 +76,18 @@ crypto { secret = ${?CRYPTO_SECRET} } +api { + limits { + batchchange-routing-max-items-limit = 100 + membership-routing-default-max-items = 100 + membership-routing-max-items-limit = 1000 + membership-routing-max-groups-list-limit = 3000 + recordset-routing-default-max-items= 100 + zone-routing-default-max-items = 100 + zone-routing-max-items-limit = 100 + } +} + http.port = 9001 http.port = ${?PORTAL_PORT} diff --git a/modules/api/src/main/resources/application.conf b/modules/api/src/main/resources/application.conf index e2d8f8f98..c1ee28ca9 100644 --- a/modules/api/src/main/resources/application.conf +++ b/modules/api/src/main/resources/application.conf @@ -164,6 +164,17 @@ vinyldns { port=${?API_SERVICE_PORT} } + api { + limits { + batchchange-routing-max-items-limit = 100 + membership-routing-default-max-items = 100 + membership-routing-max-items-limit = 1000 + membership-routing-max-groups-list-limit = 3000 + recordset-routing-default-max-items= 100 + zone-routing-default-max-items = 100 + zone-routing-max-items-limit = 100 + } + } approved-name-servers = [ "172.17.42.1.", diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala index 43b1047ad..f361c3bc4 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetService.scala @@ -611,12 +611,13 @@ class RecordSetService( def listFailedRecordSetChanges( authPrincipal: AuthPrincipal, + zoneId: Option[String] = None, startFrom: Int= 0, - maxItems: Int = 100, + maxItems: Int = 100 ): Result[ListFailedRecordSetChangesResponse] = for { recordSetChangesFailedResults <- recordChangeRepository - .listFailedRecordSetChanges(maxItems, startFrom) + .listFailedRecordSetChanges(zoneId, maxItems, startFrom) .toResult[ListFailedRecordSetChangesResults] _ <- zoneAccess(recordSetChangesFailedResults.items, authPrincipal).toResult } yield diff --git a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala index aa8dae9a9..7d2fa0a43 100644 --- a/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala +++ b/modules/api/src/main/scala/vinyldns/api/domain/record/RecordSetServiceAlgebra.scala @@ -120,6 +120,7 @@ trait RecordSetServiceAlgebra { def listFailedRecordSetChanges( authPrincipal: AuthPrincipal, + zoneId: Option[String], startFrom: Int, maxItems: Int ): Result[ListFailedRecordSetChangesResponse] diff --git a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala index 42ebe904c..2fa8e7254 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/RecordSetRouting.scala @@ -269,7 +269,7 @@ class RecordSetRoute( } } } ~ - path("metrics" / "health" / "recordsetchangesfailure") { + path("metrics" / "health" / "zones" / Segment / "recordsetchangesfailure") {zoneId => (get & monitor("Endpoint.listFailedRecordSetChanges")) { parameters("startFrom".as[Int].?(0), "maxItems".as[Int].?(DEFAULT_MAX_ITEMS)) { (startFrom: Int, maxItems: Int) => @@ -279,7 +279,7 @@ class RecordSetRoute( errorMsg = s"maxItems was $maxItems, maxItems must be between 0 exclusive " + s"and $DEFAULT_MAX_ITEMS inclusive" ){ - authenticateAndExecute(recordSetService.listFailedRecordSetChanges(_, startFrom, maxItems)) { + authenticateAndExecute(recordSetService.listFailedRecordSetChanges(_, Some(zoneId), startFrom, maxItems)) { changes => complete(StatusCodes.OK, changes) } diff --git a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala index a54131655..a557f9edc 100644 --- a/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/domain/record/RecordSetServiceSpec.scala @@ -2017,11 +2017,11 @@ class RecordSetServiceSpec doReturn(IO.pure(ListFailedRecordSetChangesResults(completeRecordSetChanges))) .when(mockRecordChangeRepo) - .listFailedRecordSetChanges(100,0) + .listFailedRecordSetChanges(Some(okZone.id),100,0) val result: ListFailedRecordSetChangesResponse = - underTest.listFailedRecordSetChanges(authPrincipal = okAuth).value.unsafeRunSync().toOption.get + underTest.listFailedRecordSetChanges(authPrincipal = okAuth,Some(okZone.id)).value.unsafeRunSync().toOption.get val changesWithName = ListFailedRecordSetChangesResponse( @@ -2042,11 +2042,11 @@ class RecordSetServiceSpec doReturn(IO.pure(ListFailedRecordSetChangesResults(completeRecordSetChanges))) .when(mockRecordChangeRepo) - .listFailedRecordSetChanges(3,2) + .listFailedRecordSetChanges(Some(okZone.id),3,2) val result: ListFailedRecordSetChangesResponse = - underTest.listFailedRecordSetChanges(authPrincipal = okAuth,2,3).value.unsafeRunSync().toOption.get + underTest.listFailedRecordSetChanges(authPrincipal = okAuth,Some(okZone.id),2,3).value.unsafeRunSync().toOption.get val changesWithName = ListFailedRecordSetChangesResponse( diff --git a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala index bba670e36..62c36bdfd 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/RecordSetRoutingSpec.scala @@ -421,9 +421,6 @@ class RecordSetRoutingSpec private val failedChangesWithUserName = List(rsChange1.copy(status = RecordSetChangeStatus.Failed) , rsChange2.copy(status = RecordSetChangeStatus.Failed)) - private val listFailedRecordSetChangeResponse = ListFailedRecordSetChangesResponse( - failedChangesWithUserName,0,0,100 - ) class TestService extends RecordSetServiceAlgebra { @@ -585,14 +582,17 @@ class RecordSetRoutingSpec def listFailedRecordSetChanges( authPrincipal: AuthPrincipal, + zoneId:Option[String], startFrom: Int, maxItems: Int ): Result[ListFailedRecordSetChangesResponse] = { - val outcome = authPrincipal match { - case _ => Right(listFailedRecordSetChangeResponse) - } - outcome.toResult - } + zoneId match { + case Some(zoneNotFound.id) => Left(ZoneNotFoundError(s"$zoneId")) + case Some(notAuthorizedZone.id) => Left(NotAuthorizedError("no way")) + case _ => authPrincipal match { + case _ => Right(ListFailedRecordSetChangesResponse(failedChangesWithUserName,0,startFrom,maxItems)) + } + }}.toResult def searchRecordSets( startFrom: Option[String], @@ -942,7 +942,7 @@ class RecordSetRoutingSpec val rsChangeFailed1 = rsChange1.copy(status = RecordSetChangeStatus.Failed) val rsChangeFailed2 = rsChange2.copy(status = RecordSetChangeStatus.Failed) - Get(s"/metrics/health/recordsetchangesfailure") ~> recordSetRoute ~> check { + Get(s"/metrics/health/zones/${okZone.id}/recordsetchangesfailure") ~> recordSetRoute ~> check { val changes = responseAs[ListFailedRecordSetChangesResponse] changes.failedRecordSetChanges.map(_.id) shouldBe List(rsChangeFailed1.id, rsChangeFailed2.id) diff --git a/modules/api/src/universal/conf/application.conf b/modules/api/src/universal/conf/application.conf index 9d49ee6a1..ff70827df 100644 --- a/modules/api/src/universal/conf/application.conf +++ b/modules/api/src/universal/conf/application.conf @@ -161,6 +161,18 @@ vinyldns { port=${?API_SERVICE_PORT} } + api { + limits { + batchchange-routing-max-items-limit = 100 + membership-routing-default-max-items = 100 + membership-routing-max-items-limit = 1000 + membership-routing-max-groups-list-limit = 3000 + recordset-routing-default-max-items= 100 + zone-routing-default-max-items = 100 + zone-routing-max-items-limit = 100 + } + } + approved-name-servers = [ "172.17.42.1.", diff --git a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala index b691c845e..6b5e507ac 100644 --- a/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala +++ b/modules/core/src/main/scala/vinyldns/core/domain/record/RecordChangeRepository.scala @@ -35,7 +35,8 @@ trait RecordChangeRepository extends Repository { def getRecordSetChange(zoneId: String, changeId: String): IO[Option[RecordSetChange]] - def listFailedRecordSetChanges(maxItems: Int = 100, + def listFailedRecordSetChanges(zoneId: Option[String], + maxItems: Int = 100, startFrom: Int = 0 ): IO[ListFailedRecordSetChangesResults] diff --git a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala index 813111e33..397db1674 100644 --- a/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala +++ b/modules/mysql/src/it/scala/vinyldns/mysql/repository/MySqlRecordChangeRepositoryIntegrationSpec.scala @@ -183,19 +183,19 @@ class MySqlRecordChangeRepositoryIntegrationSpec repo.save(db, ChangeSet(inserts)) } saveRecChange.attempt.unsafeRunSync() shouldBe right - val result = repo.listFailedRecordSetChanges(10, 0).unsafeRunSync() + val result = repo.listFailedRecordSetChanges(Some(okZone.id),10, 0).unsafeRunSync() (result.items should have).length(10) result.maxItems shouldBe 10 result.items should contain theSameElementsAs(inserts) - } + "return empty for success record changes" in { val inserts = generateInserts(okZone, 10) val saveRecChange = executeWithinTransaction { db: DB => repo.save(db, ChangeSet(inserts)) } saveRecChange.attempt.unsafeRunSync() shouldBe right - val result = repo.listFailedRecordSetChanges(5, 0).unsafeRunSync() + val result = repo.listFailedRecordSetChanges(Some(okZone.id),5, 0).unsafeRunSync() (result.items should have).length(0) result.items shouldBe List() result.nextId shouldBe 0 @@ -215,17 +215,19 @@ class MySqlRecordChangeRepositoryIntegrationSpec repo.save(db, ChangeSet(timeSpaced)) } saveRecChange.attempt.unsafeRunSync() shouldBe right - val page1 = repo.listFailedRecordSetChanges(2, 0).unsafeRunSync() - page1.nextId shouldBe 2 + val page1 = repo.listFailedRecordSetChanges(Some(okZone.id), 2, 0).unsafeRunSync() + println(page1.items) + page1.nextId shouldBe 3 page1.maxItems shouldBe 2 (page1.items should contain).theSameElementsInOrderAs(expectedOrder.take(2)) - val page2 = repo.listFailedRecordSetChanges(2, page1.nextId).unsafeRunSync() - page2.nextId shouldBe 4 + val page2 = repo.listFailedRecordSetChanges(Some(okZone.id), 2, page1.nextId).unsafeRunSync() + println(page2.items) + page2.nextId shouldBe 6 page2.maxItems shouldBe 2 - (page2.items should contain).theSameElementsInOrderAs(expectedOrder.slice(2, 4)) + (page2.items should contain).theSameElementsInOrderAs(expectedOrder.slice(3, 5)) - val page3 = repo.listFailedRecordSetChanges(2, page2.nextId).unsafeRunSync() + val page3 = repo.listFailedRecordSetChanges(Some(okZone.id), 2, 4).unsafeRunSync() page3.nextId shouldBe 0 page3.maxItems shouldBe 2 page3.items should contain theSameElementsAs expectedOrder.slice(4, 5) diff --git a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala index f847152f5..47e085cc8 100644 --- a/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala +++ b/modules/mysql/src/main/scala/vinyldns/mysql/repository/MySqlRecordChangeRepository.scala @@ -63,6 +63,7 @@ class MySqlRecordChangeRepository sql""" |SELECT data | FROM record_change + | WHERE zone_id = {zoneId} | ORDER BY created DESC """.stripMargin @@ -170,16 +171,17 @@ class MySqlRecordChangeRepository } } - def listFailedRecordSetChanges(maxItems: Int, startFrom: Int): IO[ListFailedRecordSetChangesResults] = + def listFailedRecordSetChanges(zoneId: Option[String], maxItems: Int, startFrom: Int): IO[ListFailedRecordSetChangesResults] = monitor("repo.RecordChange.listFailedRecordSetChanges") { IO { DB.readOnly { implicit s => val queryResult = LIST_RECORD_CHANGES + .bindByName('zoneId -> zoneId.get) .map(toRecordSetChange) .list() .apply() val failedRecordSetChanges = queryResult.filter(rc => rc.status == RecordSetChangeStatus.Failed).drop(startFrom).take(maxItems) - val nextId = if (failedRecordSetChanges.size < maxItems) 0 else startFrom + maxItems + val nextId = if (failedRecordSetChanges.size < maxItems) 0 else startFrom + maxItems + 1 ListFailedRecordSetChangesResults(failedRecordSetChanges,nextId,startFrom,maxItems) } } diff --git a/modules/portal/app/models/Meta.scala b/modules/portal/app/models/Meta.scala index 7d69e3e5c..b04e18cf0 100644 --- a/modules/portal/app/models/Meta.scala +++ b/modules/portal/app/models/Meta.scala @@ -24,7 +24,8 @@ case class Meta( defaultTtl: Long, manualBatchChangeReviewEnabled: Boolean, scheduledBatchChangesEnabled: Boolean, - portalUrl: String + portalUrl: String, + maxGroupItemsDisplay: Int ) object Meta { def apply(config: Configuration): Meta = @@ -35,6 +36,7 @@ object Meta { config.getOptional[Long]("default-ttl").getOrElse(7200L), config.getOptional[Boolean]("manual-batch-review-enabled").getOrElse(false), config.getOptional[Boolean]("scheduled-changes-enabled").getOrElse(false), - config.getOptional[String]("portal.vinyldns.url").getOrElse("http://localhost:9001") + config.getOptional[String]("portal.vinyldns.url").getOrElse("http://localhost:9001"), + config.getOptional[Int]("api.limits.membership-routing-max-groups-list-limit").getOrElse(3000) ) } diff --git a/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html b/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html index 14689e3e3..712de04c2 100644 --- a/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html +++ b/modules/portal/app/views/dnsChanges/dnsChangeNew.scala.html @@ -3,7 +3,7 @@ @content = {