add Redis cache and some clean up
- update and cleanup in README.md - update getIdentity to throw response exception with 401 in JwtService - update http files to use identity from env - add cacheable to getIdentityFromDevice in DeviceRegistryService - add RedisCacheConfiguration - add CacheProperty - set up cache property - set up data redis cache
This commit is contained in:
90
README.md
90
README.md
@@ -5,26 +5,31 @@ Classes and endpoints, to shape and to steer, Devices and sensors, their purpose
|
|||||||
## Properties for deployment
|
## Properties for deployment
|
||||||
|
|
||||||
| name | required | info |
|
| name | required | info |
|
||||||
|----------------------------------------------|----------|----------------------------------------------|
|
|----------------------------------------------|:--------:|----------------------------------------------|
|
||||||
| spring.profiles.active | * | Spring Boot environment |
|
| spring.profiles.active | ✓ | Spring Boot environment |
|
||||||
| server.port | * | HTTP port |
|
| server.port | ✓ | HTTP port |
|
||||||
| server.ssl.enabled | * | HTTP Enable SSL |
|
| server.ssl.enabled | ✓ | HTTP Enable SSL |
|
||||||
| server.ssl.key-store | * | HTTP Keystore |
|
| server.ssl.key-store | ✓ | HTTP Keystore |
|
||||||
| server.ssl.key-store-type | * | HTTP Cert Type |
|
| server.ssl.key-store-type | ✓ | HTTP Cert Type |
|
||||||
| server.ssl.key-store-password | ** | HTTP Cert Pass |
|
| server.ssl.key-store-password | ✗ | HTTP Cert Pass |
|
||||||
| jwt.public-key | * | JWT public key |
|
| spring.cache.type | | Cache type (redis) |
|
||||||
| device-registry.url | * | Device Register URL |
|
| spring.data.redis.host | ✓ | Redis host |
|
||||||
| device-data.url | * | Device Data URL |
|
| spring.data.redis.port | | Redis port |
|
||||||
| device-configuration.url | * | Device Configuration URL |
|
| spring.data.redis.database | ✓ | Redis database |
|
||||||
|
| cache.time-to-live | | Cache time to live (minutes) |
|
||||||
|
| jwt.public-key | ✓ | JWT public key |
|
||||||
|
| device-registry.url | ✓ | Device Register URL |
|
||||||
|
| device-data.url | ✓ | Device Data URL |
|
||||||
|
| device-configuration.url | ✓ | Device Configuration URL |
|
||||||
| management.influx.metrics.export.api-version | | InfluxDB API version |
|
| management.influx.metrics.export.api-version | | InfluxDB API version |
|
||||||
| management.influx.metrics.export.enabled | | Enable/Disable exporting metrics to InfluxDB |
|
| management.influx.metrics.export.enabled | | Enable/Disable exporting metrics to InfluxDB |
|
||||||
| management.influx.metrics.export.bucket | * | InfluxDB bucket name |
|
| management.influx.metrics.export.bucket | ✓ | InfluxDB bucket name |
|
||||||
| management.influx.metrics.export.org | * | InfluxDB organization |
|
| management.influx.metrics.export.org | ✓ | InfluxDB organization |
|
||||||
| management.influx.metrics.export.token | ** | InfluxDB token |
|
| management.influx.metrics.export.token | ✗ | InfluxDB token |
|
||||||
| management.influx.metrics.export.uri | * | InfluxDB URL |
|
| management.influx.metrics.export.uri | ✓ | InfluxDB URL |
|
||||||
| management.metrics.tags.application | * | Application instance tag for metrics |
|
| management.metrics.tags.application | ✓ | Application instance tag for metrics |
|
||||||
|
|
||||||
Required: * can be stored as text, and ** need to be stored as secret.
|
*Required: ✓ can be stored as text, and ✗ need to be stored as secret.*
|
||||||
|
|
||||||
## Releasing Service
|
## Releasing Service
|
||||||
|
|
||||||
@@ -34,43 +39,32 @@ Run `release.sh` script from `master` branch.
|
|||||||
|
|
||||||
### Developer Keystore
|
### Developer Keystore
|
||||||
|
|
||||||
1. Open `hosts` file:
|
We use a keystore to enable HTTPS for our API. To set up your developer environment for local development, please refer to [generate keystore](https://github.com/swordsteel/hlaeja-development/blob/master/doc/keystore.md) documentation. When generating and exporting the certificate for local development, please store it in the `./cert/keystore.p12` folder at the project root.
|
||||||
* On Unix-like systems (Linux, macOS), this directory is typically `/etc/hosts`.
|
|
||||||
* On Windows, this directory is typically `%SystemRoot%\System32\drivers\etc\hosts`.
|
|
||||||
|
|
||||||
2. Add the following lines to the `hosts` file:
|
|
||||||
```text
|
|
||||||
127.0.0.1 deviceapi # Hlæja Device API
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Generate Keystores
|
|
||||||
```shell
|
|
||||||
keytool -genkeypair -alias device-api -keyalg RSA -keysize 2048 -validity 3650 -dname "CN=deviceapi" -keypass password -keystore ./cert/keystore.p12 -storetype PKCS12 -storepass password
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Export the public certificate
|
|
||||||
```shell
|
|
||||||
keytool -export -alias device-api -keystore ./cert/keystore.p12 -storepass password -file ./cert/device-api.cer -rfc
|
|
||||||
```
|
|
||||||
|
|
||||||
### Public RSA Key
|
### Public RSA Key
|
||||||
|
|
||||||
To validate devices, copy file named `public_key.pem` from `./cert` generated for local development in **Hlæja Device Register** in to `./cert`.
|
This service uses the public key from **[Hlæja Device Register](https://github.com/swordsteel/hlaeja-device-registry)** to identify devices. To set up device identification for local development, copy the `public_key.pem` file from the `./cert` directory in **Hlæja Device Register** into the `./cert` directory of this project.
|
||||||
|
|
||||||
### Global gradle properties
|
*Note: For more information on generating RSA keys, please refer to our [generate RSA key](https://github.com/swordsteel/hlaeja-development/blob/master/doc/rsa_key.md) documentation.*
|
||||||
|
|
||||||
To authenticate with Gradle to access repositories that require authentication, you can set your user and token in the `gradle.properties` file.
|
### Global Settings
|
||||||
|
|
||||||
Here's how you can do it:
|
This services rely on a set of global settings to configure development environments. These settings, managed through Gradle properties or environment variables.
|
||||||
|
|
||||||
1. Open or create the `gradle.properties` file in your Gradle user home directory:
|
*Note: For more information on global properties, please refer to our [global settings](https://github.com/swordsteel/hlaeja-development/blob/master/doc/global_settings.md) documentation.*
|
||||||
|
|
||||||
- On Unix-like systems (Linux, macOS), this directory is typically `~/.gradle/`.
|
#### Gradle Properties
|
||||||
- On Windows, this directory is typically `C:\Users\<YourUsername>\.gradle\`.
|
|
||||||
|
|
||||||
2. Add the following lines to the `gradle.properties` file:
|
```properties
|
||||||
```properties
|
repository.user=your_user
|
||||||
repository.user=your_user
|
repository.token=your_token_value
|
||||||
repository.token=your_token_value
|
influxdb.token=your_token_value
|
||||||
```
|
```
|
||||||
or use environment variables `REPOSITORY_USER` and `REPOSITORY_TOKEN`
|
|
||||||
|
#### Environment Variables
|
||||||
|
|
||||||
|
```properties
|
||||||
|
REPOSITORY_USER=your_user
|
||||||
|
REPOSITORY_TOKEN=your_token_value
|
||||||
|
INFLUXDB_TOKEN=your_token_value
|
||||||
|
```
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ dependencies {
|
|||||||
implementation(hlaeja.micrometer.registry.influx)
|
implementation(hlaeja.micrometer.registry.influx)
|
||||||
implementation(hlaeja.library.hlaeja.common.messages)
|
implementation(hlaeja.library.hlaeja.common.messages)
|
||||||
implementation(hlaeja.springboot.starter.actuator)
|
implementation(hlaeja.springboot.starter.actuator)
|
||||||
|
implementation(hlaeja.springboot.starter.cache)
|
||||||
|
implementation(hlaeja.springboot.starter.redis)
|
||||||
implementation(hlaeja.springboot.starter.webflux)
|
implementation(hlaeja.springboot.starter.webflux)
|
||||||
|
|
||||||
runtimeOnly(hlaeja.jjwt.impl)
|
runtimeOnly(hlaeja.jjwt.impl)
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
### get measurement
|
### get configuration
|
||||||
GET {{hostname}}/configuration
|
GET {{hostname}}/configuration
|
||||||
Identity: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
Identity: {{identity}}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
"development": {
|
"development": {
|
||||||
"hostname": "https://localhost:8443"
|
"hostname": "https://localhost:8443",
|
||||||
|
"identity": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
},
|
},
|
||||||
"docker": {
|
"docker": {
|
||||||
"hostname": "https://localhost:9000"
|
"hostname": "https://localhost:9000",
|
||||||
|
"identity": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
### get measurement
|
### get measurement
|
||||||
GET {{hostname}}/measurement
|
GET {{hostname}}/measurement
|
||||||
Identity: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
Identity: {{identity}}
|
||||||
|
|
||||||
### add measurement for all
|
### add measurement for all
|
||||||
POST {{hostname}}/measurement
|
POST {{hostname}}/measurement
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Identity: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
Identity: {{identity}}
|
||||||
|
|
||||||
{
|
{
|
||||||
"button0": 0,
|
"button0": 0,
|
||||||
@@ -16,8 +16,8 @@ Identity: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVW
|
|||||||
### add measurement for one
|
### add measurement for one
|
||||||
POST {{hostname}}/measurement
|
POST {{hostname}}/measurement
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Identity: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
Identity: {{identity}}
|
||||||
|
|
||||||
{
|
{
|
||||||
"button0": 0
|
"button0": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package ltd.hlaeja
|
package ltd.hlaeja
|
||||||
|
|
||||||
|
import ltd.hlaeja.property.CacheProperty
|
||||||
import ltd.hlaeja.property.DeviceConfigurationProperty
|
import ltd.hlaeja.property.DeviceConfigurationProperty
|
||||||
import ltd.hlaeja.property.DeviceDataProperty
|
import ltd.hlaeja.property.DeviceDataProperty
|
||||||
import ltd.hlaeja.property.DeviceRegistryProperty
|
import ltd.hlaeja.property.DeviceRegistryProperty
|
||||||
@@ -7,8 +8,11 @@ import ltd.hlaeja.property.JwtProperty
|
|||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
|
import org.springframework.cache.annotation.EnableCaching
|
||||||
|
|
||||||
|
@EnableCaching
|
||||||
@EnableConfigurationProperties(
|
@EnableConfigurationProperties(
|
||||||
|
CacheProperty::class,
|
||||||
DeviceConfigurationProperty::class,
|
DeviceConfigurationProperty::class,
|
||||||
DeviceDataProperty::class,
|
DeviceDataProperty::class,
|
||||||
DeviceRegistryProperty::class,
|
DeviceRegistryProperty::class,
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package ltd.hlaeja.configuration
|
||||||
|
|
||||||
|
import java.time.Duration
|
||||||
|
import ltd.hlaeja.exception.CacheException
|
||||||
|
import ltd.hlaeja.property.CacheProperty
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheConfiguration
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheManager
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class RedisCacheConfiguration(
|
||||||
|
private val cacheProperty: CacheProperty,
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun cacheManager(
|
||||||
|
redisTemplate: RedisTemplate<String, String>,
|
||||||
|
): RedisCacheManager = redisTemplate.connectionFactory
|
||||||
|
?.let { RedisCacheManager.builder(it).cacheDefaults(getRedisCacheConfiguration()).build() }
|
||||||
|
?: throw CacheException("Redis connection factory is not set")
|
||||||
|
|
||||||
|
private fun getRedisCacheConfiguration(): RedisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
|
||||||
|
.entryTtl(Duration.ofMinutes(cacheProperty.timeToLive))
|
||||||
|
}
|
||||||
23
src/main/kotlin/ltd/hlaeja/exception/CacheException.kt
Normal file
23
src/main/kotlin/ltd/hlaeja/exception/CacheException.kt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package ltd.hlaeja.exception
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class CacheException : Exception {
|
||||||
|
|
||||||
|
constructor() : super()
|
||||||
|
|
||||||
|
constructor(message: String) : super(message)
|
||||||
|
|
||||||
|
constructor(cause: Throwable) : super(cause)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
message: String,
|
||||||
|
cause: Throwable,
|
||||||
|
) : super(message, cause)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
message: String,
|
||||||
|
cause: Throwable,
|
||||||
|
enableSuppression: Boolean,
|
||||||
|
writableStackTrace: Boolean,
|
||||||
|
) : super(message, cause, enableSuppression, writableStackTrace)
|
||||||
|
}
|
||||||
8
src/main/kotlin/ltd/hlaeja/property/CacheProperty.kt
Normal file
8
src/main/kotlin/ltd/hlaeja/property/CacheProperty.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package ltd.hlaeja.property
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "cache")
|
||||||
|
data class CacheProperty(
|
||||||
|
val timeToLive: Long,
|
||||||
|
)
|
||||||
@@ -7,6 +7,7 @@ import java.util.UUID
|
|||||||
import ltd.hlaeja.library.deviceRegistry.Identity
|
import ltd.hlaeja.library.deviceRegistry.Identity
|
||||||
import ltd.hlaeja.property.DeviceRegistryProperty
|
import ltd.hlaeja.property.DeviceRegistryProperty
|
||||||
import ltd.hlaeja.util.deviceRegistryIdentityDevice
|
import ltd.hlaeja.util.deviceRegistryIdentityDevice
|
||||||
|
import org.springframework.cache.annotation.Cacheable
|
||||||
import org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE
|
import org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.web.ErrorResponseException
|
import org.springframework.web.ErrorResponseException
|
||||||
@@ -31,6 +32,7 @@ class DeviceRegistryService(
|
|||||||
.description("Number of failed device identity calls")
|
.description("Number of failed device identity calls")
|
||||||
.register(meterRegistry)
|
.register(meterRegistry)
|
||||||
|
|
||||||
|
@Cacheable(value = ["identity"], key = "#device")
|
||||||
suspend fun getIdentityFromDevice(
|
suspend fun getIdentityFromDevice(
|
||||||
device: UUID,
|
device: UUID,
|
||||||
): Identity.Response = try {
|
): Identity.Response = try {
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package ltd.hlaeja.service
|
package ltd.hlaeja.service
|
||||||
|
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import io.jsonwebtoken.JwtException
|
||||||
import io.jsonwebtoken.JwtParser
|
import io.jsonwebtoken.JwtParser
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import ltd.hlaeja.library.deviceRegistry.Identity
|
import ltd.hlaeja.library.deviceRegistry.Identity
|
||||||
import ltd.hlaeja.property.JwtProperty
|
import ltd.hlaeja.property.JwtProperty
|
||||||
import ltd.hlaeja.util.PublicKeyProvider
|
import ltd.hlaeja.util.PublicKeyProvider
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
|
||||||
private val log = KotlinLogging.logger {}
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
@@ -23,8 +26,13 @@ class JwtService(
|
|||||||
|
|
||||||
suspend fun getIdentity(
|
suspend fun getIdentity(
|
||||||
identityToken: String,
|
identityToken: String,
|
||||||
): Identity.Response = readIdentity(identityToken)
|
): Identity.Response = try {
|
||||||
|
readIdentity(identityToken)
|
||||||
.let { deviceRegistry.getIdentityFromDevice(it) }
|
.let { deviceRegistry.getIdentityFromDevice(it) }
|
||||||
|
} catch (e: JwtException) {
|
||||||
|
log.warn { e.localizedMessage }
|
||||||
|
throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun readIdentity(
|
private suspend fun readIdentity(
|
||||||
identity: String,
|
identity: String,
|
||||||
|
|||||||
@@ -39,6 +39,11 @@
|
|||||||
"name": "device-configuration.url",
|
"name": "device-configuration.url",
|
||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
"description": "Url for device configuration service."
|
"description": "Url for device configuration service."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cache.time-to-live",
|
||||||
|
"type": "java.lang.Long",
|
||||||
|
"description": "Cache expiration in minutes."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ spring:
|
|||||||
os:
|
os:
|
||||||
name: "%APP_BUILD_OS_NAME%"
|
name: "%APP_BUILD_OS_NAME%"
|
||||||
version: "%APP_BUILD_OS_VERSION%"
|
version: "%APP_BUILD_OS_VERSION%"
|
||||||
|
cache:
|
||||||
|
type: redis
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
port: 6379
|
||||||
|
database: 1
|
||||||
|
|
||||||
management:
|
management:
|
||||||
endpoints:
|
endpoints:
|
||||||
@@ -29,6 +35,9 @@ management:
|
|||||||
bucket: hlaeja
|
bucket: hlaeja
|
||||||
org: hlaeja_ltd
|
org: hlaeja_ltd
|
||||||
|
|
||||||
|
cache:
|
||||||
|
time-to-live: 10
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
public-key: cert/public_key.pem
|
public-key: cert/public_key.pem
|
||||||
|
|
||||||
@@ -40,6 +49,9 @@ spring:
|
|||||||
config:
|
config:
|
||||||
activate:
|
activate:
|
||||||
on-profile: development
|
on-profile: development
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: localhost
|
||||||
|
|
||||||
management:
|
management:
|
||||||
metrics:
|
metrics:
|
||||||
@@ -76,6 +88,9 @@ spring:
|
|||||||
config:
|
config:
|
||||||
activate:
|
activate:
|
||||||
on-profile: docker
|
on-profile: docker
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: Redis
|
||||||
|
|
||||||
management:
|
management:
|
||||||
metrics:
|
metrics:
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ device-data:
|
|||||||
url: http://localhost:9020
|
url: http://localhost:9020
|
||||||
device-configuration:
|
device-configuration:
|
||||||
url: http://localhost:9030
|
url: http://localhost:9030
|
||||||
|
cache:
|
||||||
|
time-to-live: 10
|
||||||
|
|||||||
Reference in New Issue
Block a user