main request, mdm properties set
This commit is contained in:
@@ -91,3 +91,11 @@ server:
|
|||||||
|
|
||||||
classifier:
|
classifier:
|
||||||
stations-url: ${CLASSIFIER_STATIONS_URL:https://ordinis.k8s.265.nstart.cloud/api/v1/ground_station/records}
|
stations-url: ${CLASSIFIER_STATIONS_URL:https://ordinis.k8s.265.nstart.cloud/api/v1/ground_station/records}
|
||||||
|
# cache TTL
|
||||||
|
stations-cache-ttl: ${CLASSIFIER_STATIONS_CACHE_TTL:15m}
|
||||||
|
# MDM retries
|
||||||
|
stations-refresh-attempts: ${CLASSIFIER_STATIONS_REFRESH_ATTEMPTS:3}
|
||||||
|
# MDM retry delay
|
||||||
|
stations-refresh-retry-delay: ${CLASSIFIER_STATIONS_REFRESH_RETRY_DELAY:300ms}
|
||||||
|
# MDM failure cooldown
|
||||||
|
stations-refresh-failure-cooldown: ${CLASSIFIER_STATIONS_REFRESH_FAILURE_COOLDOWN:5s}
|
||||||
|
|||||||
+2
@@ -1,9 +1,11 @@
|
|||||||
package space.nstart.pcp.pcp_stations_service
|
package space.nstart.pcp.pcp_stations_service
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@ConfigurationPropertiesScan
|
||||||
class PcpStationsServiceApplication
|
class PcpStationsServiceApplication
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package space.nstart.pcp.pcp_stations_service.configuration
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "classifier")
|
||||||
|
data class ClassifierProperties(
|
||||||
|
val stationsUrl: String = "https://ordinis.k8s.265.nstart.cloud/api/v1/ground_station/records",
|
||||||
|
val stationsCacheTtl: Duration = Duration.ofMinutes(15),
|
||||||
|
val stationsRefreshAttempts: Int = 3,
|
||||||
|
val stationsRefreshRetryDelay: Duration = Duration.ofMillis(300),
|
||||||
|
val stationsRefreshFailureCooldown: Duration = Duration.ofSeconds(5)
|
||||||
|
)
|
||||||
+81
-25
@@ -3,11 +3,12 @@ package space.nstart.pcp.pcp_stations_service.integration
|
|||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.web.reactive.function.client.WebClient
|
import org.springframework.web.reactive.function.client.WebClient
|
||||||
import org.springframework.web.reactive.function.client.WebClientException
|
import org.springframework.web.reactive.function.client.WebClientRequestException
|
||||||
import org.springframework.web.reactive.function.client.WebClientResponseException
|
import org.springframework.web.reactive.function.client.WebClientResponseException
|
||||||
|
import space.nstart.pcp.pcp_stations_service.configuration.ClassifierProperties
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.PositionDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.PositionDTO
|
||||||
import space.nstart.pcp.pcp_types_lib.dto.ballistics.StationDTO
|
import space.nstart.pcp.pcp_types_lib.dto.ballistics.StationDTO
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
@@ -16,41 +17,78 @@ import java.util.UUID
|
|||||||
@Component
|
@Component
|
||||||
class NsiStationClient(
|
class NsiStationClient(
|
||||||
webClientBuilder: WebClient.Builder,
|
webClientBuilder: WebClient.Builder,
|
||||||
|
private val classifierProperties: ClassifierProperties,
|
||||||
@Value("\${classifier.stations-url:https://ordinis.k8s.265.nstart.cloud/api/v1/ground_station/records}")
|
|
||||||
private val stationsUrl: String
|
|
||||||
) {
|
) {
|
||||||
private val log = LoggerFactory.getLogger(javaClass)
|
private val log = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
private val webClient: WebClient = webClientBuilder.build()
|
private val webClient: WebClient = webClientBuilder.build()
|
||||||
|
|
||||||
|
private fun classifyResponseException(
|
||||||
|
ex: WebClientResponseException
|
||||||
|
): NsiStationsException {
|
||||||
|
val statusCode = ex.statusCode.value()
|
||||||
|
|
||||||
|
return if (
|
||||||
|
statusCode == 408 ||
|
||||||
|
statusCode == 429 ||
|
||||||
|
statusCode in 500..599
|
||||||
|
) {
|
||||||
|
NsiStationsUnavailableException(
|
||||||
|
classifierUrl = classifierProperties.stationsUrl,
|
||||||
|
statusCode = statusCode,
|
||||||
|
cause = ex
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
NsiStationsResponseException(
|
||||||
|
classifierUrl = classifierProperties.stationsUrl,
|
||||||
|
statusCode = statusCode,
|
||||||
|
cause = ex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun fetchStations(): List<StationDTO> {
|
fun fetchStations(): List<StationDTO> {
|
||||||
val records = try {
|
log.info(
|
||||||
|
"Fetching stations from NSI. url={}",
|
||||||
|
classifierProperties.stationsUrl
|
||||||
|
)
|
||||||
|
|
||||||
|
val response: List<NsiStationRecord> = try {
|
||||||
webClient.get()
|
webClient.get()
|
||||||
.uri(stationsUrl)
|
.uri(classifierProperties.stationsUrl)
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(Array<NsiStationRecord>::class.java)
|
.bodyToMono(Array<NsiStationRecord>::class.java)
|
||||||
.block(Duration.ofSeconds(15))
|
.block(Duration.ofSeconds(10))
|
||||||
?.toList()
|
?.toList()
|
||||||
?: emptyList()
|
?: throw NsiStationsUnavailableException(
|
||||||
|
classifierUrl = classifierProperties.stationsUrl,
|
||||||
|
statusCode = null,
|
||||||
|
cause = IllegalStateException("NSI response body is absent")
|
||||||
|
)
|
||||||
|
} catch (ex: NsiStationsException) {
|
||||||
|
throw ex
|
||||||
} catch (ex: WebClientResponseException) {
|
} catch (ex: WebClientResponseException) {
|
||||||
log.error(
|
throw classifyResponseException(ex)
|
||||||
"NSI stations request failed. url={}, status={}, body={}",
|
} catch (ex: WebClientRequestException) {
|
||||||
stationsUrl,
|
throw NsiStationsUnavailableException(
|
||||||
ex.statusCode,
|
classifierUrl = classifierProperties.stationsUrl,
|
||||||
ex.responseBodyAsString,
|
statusCode = null,
|
||||||
ex
|
cause = ex
|
||||||
|
)
|
||||||
|
} catch (ex: IllegalStateException) {
|
||||||
|
/*
|
||||||
|
* Сюда, в частности, попадает timeout от block(Duration).
|
||||||
|
*/
|
||||||
|
throw NsiStationsUnavailableException(
|
||||||
|
classifierUrl = classifierProperties.stationsUrl,
|
||||||
|
statusCode = null,
|
||||||
|
cause = ex
|
||||||
)
|
)
|
||||||
throw NsiStationsUnavailableException("NSI stations request failed: $stationsUrl", ex)
|
|
||||||
} catch (ex: WebClientException) {
|
|
||||||
log.error("NSI stations request failed. url={}", stationsUrl, ex)
|
|
||||||
throw NsiStationsUnavailableException("NSI stations request failed: $stationsUrl", ex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Loaded {} station records from NSI. url={}", records.size, stationsUrl)
|
return response.mapIndexed { index, station ->
|
||||||
|
station.toStationDTO(number = index + 1)
|
||||||
return records.mapIndexed { index, record ->
|
|
||||||
record.toStationDTO(number = index + 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,11 +113,29 @@ class NsiStationClient(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NsiStationsUnavailableException(
|
sealed class NsiStationsException(
|
||||||
message: String,
|
message: String,
|
||||||
cause: Throwable
|
cause: Throwable?
|
||||||
) : RuntimeException(message, cause)
|
) : RuntimeException(message, cause)
|
||||||
|
|
||||||
|
class NsiStationsUnavailableException(
|
||||||
|
val classifierUrl: String,
|
||||||
|
val statusCode: Int?,
|
||||||
|
cause: Throwable?
|
||||||
|
) : NsiStationsException(
|
||||||
|
message = "NSI stations classifier is temporarily unavailable: $classifierUrl",
|
||||||
|
cause = cause
|
||||||
|
)
|
||||||
|
|
||||||
|
class NsiStationsResponseException(
|
||||||
|
val classifierUrl: String,
|
||||||
|
val statusCode: Int,
|
||||||
|
cause: Throwable?
|
||||||
|
) : NsiStationsException(
|
||||||
|
message = "NSI stations classifier returned HTTP $statusCode: $classifierUrl",
|
||||||
|
cause = cause
|
||||||
|
)
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
data class NsiStationRecord(
|
data class NsiStationRecord(
|
||||||
val id: String? = null,
|
val id: String? = null,
|
||||||
|
|||||||
Reference in New Issue
Block a user