diff --git a/code/tic-tac-tow-service/README.md b/code/tic-tac-tow-service/README.md
index 80a56e5..ab139ed 100644
--- a/code/tic-tac-tow-service/README.md
+++ b/code/tic-tac-tow-service/README.md
@@ -21,4 +21,14 @@ psql -U dbuser -d db
* `\h` - show help.
* `\d
` - show table.
* `select ... ;` - execute query.
- * `\q` - quit `psql`.
\ No newline at end of file
+ * `\q` - quit `psql`.
+
+* Start the full composition
+```
+./gradlew composeUp
+```
+
+* Loop curl
+```
+while true; do curl -w "\n" http://localhost:8080/status/hostname; done
+```
diff --git a/code/tic-tac-tow-service/build.gradle.kts b/code/tic-tac-tow-service/build.gradle.kts
index e176176..7abe3de 100644
--- a/code/tic-tac-tow-service/build.gradle.kts
+++ b/code/tic-tac-tow-service/build.gradle.kts
@@ -59,6 +59,14 @@ tasks.withType {
useJUnitPlatform()
}
+task("extractUberJar") {
+ dependsOn("assemble")
+ // opens the JAR containing everything...
+ from(zipTree("$buildDir/libs/${rootProject.name}-$version.jar"))
+ // ... into the 'build/dependency' folder
+ into("build/dependency")
+}
+
task("dbTestsUp") {
commandLine("docker-compose", "up", "-d", "--build", "--force-recreate", "db-tests")
}
@@ -72,6 +80,11 @@ task("dbTestsDown") {
commandLine("docker-compose", "down")
}
+task("composeUp") {
+ commandLine("docker-compose", "up", "--build", "--force-recreate")
+ dependsOn("extractUberJar")
+}
+
// from https://pinterest.github.io/ktlint/install/integrations/#custom-gradle-integration-with-kotlin-dsl
val outputDir = "${project.buildDir}/reports/ktlint/"
val inputFiles = project.fileTree(mapOf("dir" to "src", "include" to "**/*.kt"))
@@ -91,4 +104,4 @@ tasks.named("check") {
dependsOn(ktlintCheck)
dependsOn("dbTestsWait")
finalizedBy("dbTestsDown")
-}
\ No newline at end of file
+}
diff --git a/code/tic-tac-tow-service/docker-compose.yml b/code/tic-tac-tow-service/docker-compose.yml
index 6e0c501..90f317a 100644
--- a/code/tic-tac-tow-service/docker-compose.yml
+++ b/code/tic-tac-tow-service/docker-compose.yml
@@ -1,7 +1,9 @@
version: "3.3"
services:
+ # The service running the DB
db-tests:
container_name: db-tests
+ hostname: db-tests
build:
context: .
dockerfile: ./tests/Dockerfile-db-test
@@ -10,4 +12,62 @@ services:
- POSTGRES_PASSWORD=changeit
- POSTGRES_DB=db
ports:
- - 5432:5432
\ No newline at end of file
+ - 5432:5432
+
+ # spring-service-1 and spring-service-2 are used to illustrate scenarios with a fixed number of servers
+ # with static and well know names.
+ spring-service-1:
+ container_name: spring-service-1
+ hostname: spring-service-1
+ build:
+ context: .
+ dockerfile: ./tests/Dockerfile-spring
+ environment:
+ PORT: 8081
+ POSTGRES_URI: "jdbc:postgresql://db-tests:5432/db?user=dbuser&password=changeit"
+ ports:
+ - 8081:8081
+
+ spring-service-2:
+ container_name: spring-service-2
+ hostname: spring-service-2
+ build:
+ context: .
+ dockerfile: ./tests/Dockerfile-spring
+ environment:
+ PORT: 8082
+ POSTGRES_URI: "jdbc:postgresql://db-tests:5432/db?user=dbuser&password=changeit"
+ ports:
+ - 8082:8082
+
+ # spring-service is used to illustrate scenario with a dynamic number of servers
+ # without static and well know names.
+ # We will use docker compose scaling to create multiple instances of this service
+ spring-service:
+ build:
+ context: .
+ dockerfile: ./tests/Dockerfile-spring
+ environment:
+ PORT: 8080
+ POSTGRES_URI: "jdbc:postgresql://db-tests:5432/db?user=dbuser&password=changeit"
+
+ # The service running the load-balancer
+ nginx:
+ container_name: nginx
+ image: nginx
+ ports:
+ - 8080:8080
+ - 8088:8088
+ volumes:
+ - ./tests/nginx:/etc/nginx
+ depends_on:
+ - spring-service-1
+ - spring-service-2
+
+ # Just a machine running ubuntu, with 'dig' installed so that we can observe the docker compose environment.
+ ubuntu:
+ container_name: ubuntu
+ build:
+ context: .
+ dockerfile: ./tests/Dockerfile-ubuntu
+ tty: true
diff --git a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/TicTacTowApplication.kt b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/TicTacTowApplication.kt
index d36c739..365e1e4 100644
--- a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/TicTacTowApplication.kt
+++ b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/TicTacTowApplication.kt
@@ -1,7 +1,10 @@
package pt.isel.daw.tictactow
import org.jdbi.v3.core.Jdbi
+import org.postgresql.PGProperty
import org.postgresql.ds.PGSimpleDataSource
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
@@ -19,11 +22,24 @@ import java.time.Instant
@SpringBootApplication
class TicTacTowApplication {
@Bean
- fun jdbi() = Jdbi.create(
- PGSimpleDataSource().apply {
- setURL("jdbc:postgresql://localhost:5432/db?user=dbuser&password=changeit")
+ fun jdbi(): Jdbi {
+ val postgresUri =
+ System.getenv("POSTGRES_URI")
+ ?: "jdbc:postgresql://localhost:5432/db?user=dbuser&password=changeit"
+
+ val dataSource = PGSimpleDataSource().apply {
+ setURL(postgresUri)
}
- ).configure()
+
+ // Be careful not to disclose the credentials.
+ logger.info(
+ "Using PostgreSQL located at '{}:{}'",
+ dataSource.getProperty(PGProperty.PG_HOST),
+ dataSource.getProperty(PGProperty.PG_PORT),
+ )
+
+ return Jdbi.create(dataSource).configure()
+ }
@Bean
fun passwordEncoder() = BCryptPasswordEncoder()
@@ -35,6 +51,10 @@ class TicTacTowApplication {
fun clock() = object : Clock {
override fun now() = Instant.now()
}
+
+ companion object {
+ private val logger: Logger = LoggerFactory.getLogger(TicTacTowApplication::class.java)
+ }
}
// QUESTION: why cannot this be in TicTacTowApplication
diff --git a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/HomeController.kt b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/HomeController.kt
index ba573d7..8af57b5 100644
--- a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/HomeController.kt
+++ b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/HomeController.kt
@@ -9,7 +9,11 @@ import pt.isel.daw.tictactow.infra.siren
class HomeController {
@GetMapping(Uris.HOME)
- fun getHome() = siren(HomeOutputModel("Made for teaching purposes by P. Félix")) {
+ fun getHome() = siren(
+ HomeOutputModel(
+ credits = "Made for teaching purposes by P. Félix",
+ )
+ ) {
link(Uris.home(), Rels.SELF)
link(Uris.home(), Rels.HOME)
}
diff --git a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/StatusController.kt b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/StatusController.kt
new file mode 100644
index 0000000..d2e6665
--- /dev/null
+++ b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/StatusController.kt
@@ -0,0 +1,32 @@
+package pt.isel.daw.tictactow.http
+
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+import pt.isel.daw.tictactow.http.model.StatusOutputModel
+import pt.isel.daw.tictactow.infra.siren
+import pt.isel.daw.tictactow.repository.TransactionManager
+import java.net.InetAddress
+
+@RestController
+class StatusController(
+ val transactionManager: TransactionManager
+) {
+
+ @GetMapping(Uris.STATUS)
+ fun getStatus() = transactionManager.run { transaction ->
+ siren(
+ StatusOutputModel(
+ hostname = System.getenv("HOSTNAME"),
+ gamesCount = transaction.gamesRepository.count()
+ )
+ ) {
+ // For now, nothing more to add.
+ }
+ }
+
+ @GetMapping(Uris.STATUS_HOSTNAME)
+ fun getStatusHostname(): String = System.getenv("HOSTNAME")
+
+ @GetMapping(Uris.STATUS_IP)
+ fun getStatusIp(): String = InetAddress.getLocalHost().hostAddress
+}
\ No newline at end of file
diff --git a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/Uris.kt b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/Uris.kt
index 5724f38..13b0156 100644
--- a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/Uris.kt
+++ b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/Uris.kt
@@ -7,6 +7,9 @@ import java.net.URI
object Uris {
const val HOME = "/"
+ const val STATUS = "/status"
+ const val STATUS_HOSTNAME = "/status/hostname"
+ const val STATUS_IP = "/status/ip"
const val GAME_BY_ID = "/games/{gid}"
fun home(): URI = URI(HOME)
diff --git a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/model/StatusOutputModel.kt b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/model/StatusOutputModel.kt
new file mode 100644
index 0000000..669f6c8
--- /dev/null
+++ b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/http/model/StatusOutputModel.kt
@@ -0,0 +1,6 @@
+package pt.isel.daw.tictactow.http.model
+
+data class StatusOutputModel(
+ val hostname: String,
+ val gamesCount: Int,
+)
\ No newline at end of file
diff --git a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/repository/GamesRepository.kt b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/repository/GamesRepository.kt
index c1d3f6a..5b2bf97 100644
--- a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/repository/GamesRepository.kt
+++ b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/repository/GamesRepository.kt
@@ -7,4 +7,5 @@ interface GamesRepository {
fun insert(game: Game)
fun getById(id: UUID): Game?
fun update(game: Game)
+ fun count(): Int
}
\ No newline at end of file
diff --git a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/repository/jdbi/JdbiGamesRepository.kt b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/repository/jdbi/JdbiGamesRepository.kt
index 06a2985..e97b5ff 100644
--- a/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/repository/jdbi/JdbiGamesRepository.kt
+++ b/code/tic-tac-tow-service/src/main/kotlin/pt/isel/daw/tictactow/repository/jdbi/JdbiGamesRepository.kt
@@ -71,6 +71,11 @@ class JdbiGamesRepository(
.execute()
}
+ override fun count(): Int =
+ handle.createQuery("select count(*) as count from dbo.Games")
+ .mapTo()
+ .single()
+
companion object {
private fun Update.bindBoard(name: String, board: Board) = run {
bind(
diff --git a/code/tic-tac-tow-service/src/main/resources/application.properties b/code/tic-tac-tow-service/src/main/resources/application.properties
index 8b13789..50d98ae 100644
--- a/code/tic-tac-tow-service/src/main/resources/application.properties
+++ b/code/tic-tac-tow-service/src/main/resources/application.properties
@@ -1 +1 @@
-
+server.port=${port:8080}
diff --git a/code/tic-tac-tow-service/tests/Dockerfile-spring b/code/tic-tac-tow-service/tests/Dockerfile-spring
new file mode 100644
index 0000000..b593ace
--- /dev/null
+++ b/code/tic-tac-tow-service/tests/Dockerfile-spring
@@ -0,0 +1,9 @@
+FROM openjdk:17
+ARG DEPENDENCY=build/dependency
+# first layer with the external libs (i.e. the files that change the least).
+COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
+# second layer with the 'META-INF' contents.
+COPY ${DEPENDENCY}/META-INF /app/META-INF
+# last layer with the application JARs (i.e. the files that change the most).
+COPY ${DEPENDENCY}/BOOT-INF/classes /app
+ENTRYPOINT ["java","-cp","app:app/lib/*","pt.isel.daw.tictactow.TicTacTowApplicationKt"]
\ No newline at end of file
diff --git a/code/tic-tac-tow-service/tests/Dockerfile-ubuntu b/code/tic-tac-tow-service/tests/Dockerfile-ubuntu
new file mode 100644
index 0000000..1c9b234
--- /dev/null
+++ b/code/tic-tac-tow-service/tests/Dockerfile-ubuntu
@@ -0,0 +1,3 @@
+FROM ubuntu
+RUN apt -y update
+RUN apt -y install dnsutils
\ No newline at end of file
diff --git a/code/tic-tac-tow-service/tests/nginx/nginx.conf b/code/tic-tac-tow-service/tests/nginx/nginx.conf
new file mode 100644
index 0000000..d571136
--- /dev/null
+++ b/code/tic-tac-tow-service/tests/nginx/nginx.conf
@@ -0,0 +1,35 @@
+events {
+ worker_connections 1024;
+}
+
+http {
+
+ upstream static-spring-service {
+ server spring-service-1:8081 max_fails=3 fail_timeout=10s;
+ server spring-service-2:8082 max_fails=3 fail_timeout=10s;
+ }
+
+ upstream dynamic-spring-service {
+ server spring-service:8080 max_fails=3 fail_timeout=10s;
+ }
+
+ server {
+ listen 8080;
+
+ location / {
+ proxy_pass http://static-spring-service;
+ proxy_connect_timeout 5s;
+ proxy_next_upstream error timeout http_500;
+ }
+ }
+
+ server {
+ listen 8088;
+
+ location / {
+ proxy_pass http://dynamic-spring-service;
+ proxy_connect_timeout 5s;
+ proxy_next_upstream error timeout http_500;
+ }
+ }
+}
\ No newline at end of file