Skip to content
This repository was archived by the owner on Mar 27, 2023. It is now read-only.

Commit f4cf5a9

Browse files
author
Michel Zimmer
committed
1 parent e6ddb58 commit f4cf5a9

File tree

18 files changed

+306
-48
lines changed

18 files changed

+306
-48
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ LABEL org.opencontainers.image.vendor="neuland – Büro für Informatik GmbH"
1212
LABEL org.opencontainers.image.licenses="Apache-2.0"
1313
LABEL org.opencontainers.image.title="bandwhichd-server"
1414
LABEL org.opencontainers.image.description="bandwhichd server collecting measurements and calculating statistics"
15-
LABEL org.opencontainers.image.version="0.6.0-rc9"
15+
LABEL org.opencontainers.image.version="0.6.0-rc10"
1616
USER guest
1717
ENTRYPOINT ["/opt/java/openjdk/bin/java"]
1818
CMD ["-jar", "/opt/bandwhichd-server.jar"]
1919
EXPOSE 8080
2020
STOPSIGNAL SIGTERM
21-
COPY --from=build --chown=root:root /tmp/bandwhichd-server/target/scala-3.1.3/bandwhichd-server-assembly-0.6.0-rc9.jar /opt/bandwhichd-server.jar
21+
COPY --from=build --chown=root:root /tmp/bandwhichd-server/target/scala-3.1.3/bandwhichd-server-assembly-0.6.0-rc10.jar /opt/bandwhichd-server.jar

build.sbt

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ lazy val root = (project in file("."))
22
.settings(
33
organization := "de.neuland-bfi",
44
name := "bandwhichd-server",
5-
version := "0.6.0-rc9",
5+
version := "0.6.0-rc10",
66
scalaVersion := "3.1.3",
77
Compile / scalaSource := baseDirectory.value / "src" / "main" / "scala",
88
Test / scalaSource := baseDirectory.value / "src" / "test" / "scala",
@@ -22,22 +22,22 @@ lazy val root = (project in file("."))
2222
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
2323
oldStrategy(path)
2424
},
25-
libraryDependencies += "co.fs2" %% "fs2-core" % "3.2.12",
26-
libraryDependencies += "co.fs2" %% "fs2-reactive-streams" % "3.2.12",
27-
libraryDependencies += "com.comcast" %% "ip4s-core" % "3.1.3",
28-
libraryDependencies += "com.comcast" %% "ip4s-test-kit" % "3.1.3" % "test",
29-
libraryDependencies += "com.datastax.oss" % "java-driver-core" % "4.14.1",
30-
libraryDependencies += "com.dimafeng" %% "testcontainers-scala-scalatest" % "0.40.10" % "test",
31-
libraryDependencies += "io.circe" %% "circe-core" % "0.14.2",
32-
libraryDependencies += "io.circe" %% "circe-parser" % "0.14.2",
25+
libraryDependencies += "co.fs2" %% "fs2-core" % "3.3.0",
26+
libraryDependencies += "co.fs2" %% "fs2-reactive-streams" % "3.3.0",
27+
libraryDependencies += "com.comcast" %% "ip4s-core" % "3.2.0",
28+
libraryDependencies += "com.comcast" %% "ip4s-test-kit" % "3.2.0" % "test",
29+
libraryDependencies += "com.datastax.oss" % "java-driver-core" % "4.15.0",
30+
libraryDependencies += "com.dimafeng" %% "testcontainers-scala-scalatest" % "0.40.11" % "test",
31+
libraryDependencies += "io.circe" %% "circe-core" % "0.14.3",
32+
libraryDependencies += "io.circe" %% "circe-parser" % "0.14.3",
3333
libraryDependencies += "org.http4s" %% "http4s-circe" % "1.0.0-M32",
3434
libraryDependencies += "org.http4s" %% "http4s-core" % "1.0.0-M32",
3535
libraryDependencies += "org.http4s" %% "http4s-dsl" % "1.0.0-M32",
3636
libraryDependencies += "org.http4s" %% "http4s-ember-server" % "1.0.0-M32",
37-
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.12" % "test",
38-
libraryDependencies += "org.scalatestplus" %% "scalacheck-1-16" % "3.2.12.0" % "test",
39-
libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.0" % "runtime",
37+
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.14" % "test",
38+
libraryDependencies += "org.scalatestplus" %% "scalacheck-1-16" % "3.2.14.0" % "test",
39+
libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.3" % "runtime",
4040
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.3.14",
4141
libraryDependencies += "org.typelevel" %% "cats-effect-testing-scalatest" % "1.4.0" % "test",
42-
libraryDependencies += "org.typelevel" %% "log4cats-slf4j" % "2.4.0"
42+
libraryDependencies += "org.typelevel" %% "log4cats-slf4j" % "2.5.0"
4343
)

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
services:
1616
cassandra:
17-
image: cassandra:4.0.4
17+
image: cassandra:4.1
1818
ports:
1919
- 9042:9042
2020
bandwhichd-server:

src/main/scala/de/neuland/bandwhichd/server/adapter/in/v1/message/Message.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,18 @@ object Message {
5757
} yield message
5858

5959
given Codec[Measurement.NetworkConfiguration] =
60-
Codec.forProduct5(
60+
Codec.forProduct6(
6161
"machine_id",
6262
"timestamp",
63+
"maybe_os_release",
6364
"hostname",
6465
"interfaces",
6566
"open_sockets"
6667
)(Measurement.NetworkConfiguration.apply)(nc =>
6768
(
6869
nc.machineId,
6970
nc.timing,
71+
nc.maybeOsRelease,
7072
nc.hostname,
7173
nc.interfaces,
7274
nc.openSockets
@@ -157,6 +159,9 @@ object Message {
157159
given Decoder[InterfaceName] = Decoder[String].map(InterfaceName.apply)
158160
given Encoder[MachineId] = Encoder[UUID].contramap(_.value)
159161
given Decoder[MachineId] = Decoder[UUID].map(MachineId.apply)
162+
given Encoder[OsRelease.FileContents] = Encoder[String].contramap(_.value)
163+
given Decoder[OsRelease.FileContents] =
164+
Decoder[String].map(OsRelease.FileContents.apply)
160165
given Encoder[ProcessName] = Encoder[String].contramap(_.value)
161166
given Decoder[ProcessName] = Decoder[String].map(ProcessName.apply)
162167
given Encoder[Protocol] = Encoder[String].contramap(_ match

src/main/scala/de/neuland/bandwhichd/server/adapter/in/v1/stats/StatsCodecs.scala

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,56 @@ import io.circe.{Encoder, Json}
66
object StatsCodecs {
77
val encoder: Encoder[MonitoredStats] =
88
(stats: MonitoredStats) =>
9-
Json.obj(
10-
"hosts" -> Json.fromFields(
11-
stats.hosts
12-
.map(monitoredHost =>
13-
monitoredHost.hostId.uuid.toString -> Json.obj(
14-
"hostname" -> Json.fromString(monitoredHost.hostname.toString),
15-
"additional_hostnames" -> Json.fromValues(
16-
monitoredHost.additionalHostnames.map(additionalHostname =>
17-
Json.fromString(additionalHostname.toString)
18-
)
19-
),
20-
"connections" -> stats
21-
.connectionsFor(monitoredHost.hostId)
22-
.fold(Json.obj())(hostIdsToConnections => {
23-
Json.fromFields(
24-
hostIdsToConnections.map[(String, Json)]((hostId, _) => {
25-
hostId.uuid.toString -> Json.obj()
26-
})
9+
Json
10+
.obj(
11+
"hosts" -> Json.fromFields(
12+
stats.hosts
13+
.map(monitoredHost =>
14+
monitoredHost.hostId.uuid.toString -> Json.obj(
15+
"hostname" -> Json.fromString(
16+
monitoredHost.hostname.toString
17+
),
18+
"os_release" -> monitoredHost.maybeOsRelease.fold(Json.Null)(
19+
osRelease =>
20+
Json.obj(
21+
"pretty_name" -> osRelease.maybePrettyName
22+
.fold(Json.Null)(prettyName =>
23+
Json.fromString(prettyName.value)
24+
),
25+
"version_id" -> osRelease.maybeVersionId
26+
.fold(Json.Null)(versionId =>
27+
Json.fromString(versionId.value)
28+
),
29+
"id" -> osRelease.maybeId.fold(Json.Null)(id =>
30+
Json.fromString(id.value)
31+
)
32+
)
33+
),
34+
"additional_hostnames" -> Json.fromValues(
35+
monitoredHost.additionalHostnames.map(additionalHostname =>
36+
Json.fromString(additionalHostname.toString)
2737
)
28-
})
38+
),
39+
"connections" -> stats
40+
.connectionsFor(monitoredHost.hostId)
41+
.fold(Json.obj())(hostIdsToConnections => {
42+
Json.fromFields(
43+
hostIdsToConnections
44+
.map[(String, Json)]((hostId, _) => {
45+
hostId.uuid.toString -> Json.obj()
46+
})
47+
)
48+
})
49+
)
2950
)
30-
)
31-
),
32-
"unmonitoredHosts" -> Json.fromFields(
33-
stats.unidentifiedRemoteHosts.map(unidentifiedRemoteHost => {
34-
unidentifiedRemoteHost.hostId.uuid.toString -> Json.obj(
35-
"host" -> Json.fromString(unidentifiedRemoteHost.host.toString)
36-
)
37-
})
51+
),
52+
"unmonitoredHosts" -> Json.fromFields(
53+
stats.unidentifiedRemoteHosts.map(unidentifiedRemoteHost => {
54+
unidentifiedRemoteHost.hostId.uuid.toString -> Json.obj(
55+
"host" -> Json.fromString(unidentifiedRemoteHost.host.toString)
56+
)
57+
})
58+
)
3859
)
39-
)
60+
.deepDropNullValues
4061
}

src/main/scala/de/neuland/bandwhichd/server/adapter/out/CassandraMigration.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ class CassandraMigration[F[_]: Async](
1010
private val cassandraContext: CassandraContext[F]
1111
) {
1212
def migrate(configuration: Configuration): F[Unit] =
13+
for {
14+
_ <- migrateV1(configuration)
15+
_ <- migrateV2(configuration)
16+
} yield ()
17+
18+
def migrateV1(configuration: Configuration): F[Unit] =
1319
for {
1420
_ <- createCidrType(configuration)
1521
_ <- createMeasurementNetworkConfigurationInterfaceType(configuration)
@@ -18,6 +24,13 @@ class CassandraMigration[F[_]: Async](
1824
_ <- createMeasurementsTable(configuration)
1925
} yield ()
2026

27+
def migrateV2(configuration: Configuration): F[Unit] =
28+
for {
29+
_ <- addNetworkConfigurationMaybeOrReleaseToMeasurementsTable(
30+
configuration
31+
)
32+
} yield ()
33+
2134
private def createMeasurementsTable(
2235
configuration: Configuration
2336
): F[Unit] =
@@ -111,4 +124,17 @@ class CassandraMigration[F[_]: Async](
111124
.setTimeout(configuration.migrationQueryTimeout)
112125
.build()
113126
)
127+
128+
private def addNetworkConfigurationMaybeOrReleaseToMeasurementsTable(
129+
configuration: Configuration
130+
): F[Unit] =
131+
cassandraContext.executeRawExpectNoRow(
132+
SimpleStatement
133+
.builder(
134+
"alter table measurements_by_date add if not exists network_configuration_maybe_os_release text"
135+
)
136+
.setKeyspace(configuration.measurementsKeyspace)
137+
.setTimeout(configuration.migrationQueryTimeout)
138+
.build()
139+
)
114140
}

src/main/scala/de/neuland/bandwhichd/server/adapter/out/measurement/MeasurementCassandraCodecs.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ import scala.util.Try
1717

1818
object MeasurementCassandraCodecs {
1919
given Codec[Measurement[Timing]] =
20-
Codec.forProduct9(
20+
Codec.forProduct10(
2121
"date",
2222
"timestamp",
2323
"end_timestamp",
2424
"machine_id",
2525
"measurement_type",
26+
"network_configuration_maybe_os_release",
2627
"network_configuration_hostname",
2728
"network_configuration_interfaces",
2829
"network_configuration_open_sockets",
@@ -34,6 +35,7 @@ object MeasurementCassandraCodecs {
3435
endTimestamp: Timing.Timestamp,
3536
machineId: MachineId,
3637
measurementType: String,
38+
maybeOsRelease: String,
3739
hostname: Hostname,
3840
interfaces: Seq[Interface],
3941
openSockets: Seq[OpenSocket],
@@ -44,6 +46,7 @@ object MeasurementCassandraCodecs {
4446
Measurement.NetworkConfiguration(
4547
machineId = machineId,
4648
timing = timestamp,
49+
maybeOsRelease = Some(OsRelease.FileContents(maybeOsRelease)),
4750
hostname = hostname,
4851
interfaces = interfaces,
4952
openSockets = openSockets
@@ -68,6 +71,7 @@ object MeasurementCassandraCodecs {
6871
case Measurement.NetworkConfiguration(
6972
machineId,
7073
timing,
74+
maybeOsRelease,
7175
hostname,
7276
interfaces,
7377
openSockets
@@ -78,6 +82,7 @@ object MeasurementCassandraCodecs {
7882
Timing.Timestamp(Instant.EPOCH),
7983
machineId,
8084
"network_configuration",
85+
maybeOsRelease.fold("")(_.value),
8186
hostname,
8287
interfaces,
8388
openSockets,
@@ -94,6 +99,7 @@ object MeasurementCassandraCodecs {
9499
Timing.Timestamp(timing.value.normalizedStop),
95100
machineId,
96101
"network_utilization",
102+
"",
97103
Hostname.fromString("a").get,
98104
Seq.empty[Interface],
99105
Seq.empty[OpenSocket],
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package de.neuland.bandwhichd.server.domain
2+
3+
import scala.util.matching.Regex
4+
5+
case class OsRelease(
6+
maybeId: Option[OsRelease.Id],
7+
maybeVersionId: Option[OsRelease.VersionId],
8+
maybePrettyName: Option[OsRelease.PrettyName]
9+
)
10+
11+
object OsRelease {
12+
def apply(fileContents: FileContents): OsRelease = {
13+
import de.neuland.bandwhichd.server.domain.OsRelease.FileContents.findValue
14+
15+
OsRelease(
16+
maybeId = fileContents.findValue("ID").map(Id.apply),
17+
maybeVersionId =
18+
fileContents.findValue("VERSION_ID").map(VersionId.apply),
19+
maybePrettyName =
20+
fileContents.findValue("PRETTY_NAME").map(PrettyName.apply)
21+
)
22+
}
23+
24+
opaque type FileContents = String
25+
26+
object FileContents {
27+
def apply(value: String): FileContents = value
28+
29+
private val rowRegex =
30+
"""^ *([a-zA-Z]+[a-zA-Z0-9_]*) *= *(?:"([^"]*)"|([a-zA-Z0-9]+)) *$""".r
31+
32+
extension (fileContents: FileContents) {
33+
def value: String = fileContents
34+
35+
def parse: OsRelease = OsRelease.apply(fileContents)
36+
37+
def findValue(key: String): Option[String] =
38+
fileContents.value
39+
.split("\\n")
40+
.to(LazyList)
41+
.flatMap(rowRegex.findFirstMatchIn)
42+
.flatMap(_ match
43+
case Regex.Groups(foundKey, quotedValue, null)
44+
if key.equalsIgnoreCase(foundKey) =>
45+
Some(quotedValue)
46+
case Regex.Groups(foundKey, null, unquotedValue)
47+
if key.equalsIgnoreCase(foundKey) =>
48+
Some(unquotedValue)
49+
case _ => None
50+
)
51+
.headOption
52+
}
53+
}
54+
55+
opaque type Id = String
56+
57+
object Id {
58+
def apply(value: String): Id = value
59+
60+
extension (id: Id) {
61+
def value: String = id
62+
}
63+
}
64+
65+
opaque type VersionId = String
66+
67+
object VersionId {
68+
def apply(value: String): VersionId = value
69+
70+
extension (versionId: VersionId) {
71+
def value: String = versionId
72+
}
73+
}
74+
75+
opaque type PrettyName = String
76+
77+
object PrettyName {
78+
def apply(value: String): PrettyName = value
79+
80+
extension (prettyName: PrettyName) {
81+
def value: String = prettyName
82+
}
83+
}
84+
}

src/main/scala/de/neuland/bandwhichd/server/domain/measurement/Measurement.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ object Measurement {
1616
case class NetworkConfiguration(
1717
machineId: MachineId,
1818
timing: Timing.Timestamp,
19+
maybeOsRelease: Option[OsRelease.FileContents],
1920
hostname: Hostname,
2021
interfaces: Seq[Interface],
2122
openSockets: Seq[OpenSocket]

src/main/scala/de/neuland/bandwhichd/server/domain/stats/Host.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ object MachineIdHost {
4545

4646
case class MonitoredHost(
4747
hostId: HostId.MachineId,
48+
maybeOsRelease: Option[OsRelease],
4849
hostname: Hostname,
4950
additionalHostnames: Set[Hostname],
5051
interfaces: Set[Interface]

0 commit comments

Comments
 (0)