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

Commit

Permalink
Add os-release information to hosts
Browse files Browse the repository at this point in the history
  • Loading branch information
Michel Zimmer committed Oct 28, 2022
1 parent e6ddb58 commit f4cf5a9
Show file tree
Hide file tree
Showing 18 changed files with 306 additions and 48 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ LABEL org.opencontainers.image.vendor="neuland – Büro für Informatik GmbH"
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL org.opencontainers.image.title="bandwhichd-server"
LABEL org.opencontainers.image.description="bandwhichd server collecting measurements and calculating statistics"
LABEL org.opencontainers.image.version="0.6.0-rc9"
LABEL org.opencontainers.image.version="0.6.0-rc10"
USER guest
ENTRYPOINT ["/opt/java/openjdk/bin/java"]
CMD ["-jar", "/opt/bandwhichd-server.jar"]
EXPOSE 8080
STOPSIGNAL SIGTERM
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
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
26 changes: 13 additions & 13 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ lazy val root = (project in file("."))
.settings(
organization := "de.neuland-bfi",
name := "bandwhichd-server",
version := "0.6.0-rc9",
version := "0.6.0-rc10",
scalaVersion := "3.1.3",
Compile / scalaSource := baseDirectory.value / "src" / "main" / "scala",
Test / scalaSource := baseDirectory.value / "src" / "test" / "scala",
Expand All @@ -22,22 +22,22 @@ lazy val root = (project in file("."))
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(path)
},
libraryDependencies += "co.fs2" %% "fs2-core" % "3.2.12",
libraryDependencies += "co.fs2" %% "fs2-reactive-streams" % "3.2.12",
libraryDependencies += "com.comcast" %% "ip4s-core" % "3.1.3",
libraryDependencies += "com.comcast" %% "ip4s-test-kit" % "3.1.3" % "test",
libraryDependencies += "com.datastax.oss" % "java-driver-core" % "4.14.1",
libraryDependencies += "com.dimafeng" %% "testcontainers-scala-scalatest" % "0.40.10" % "test",
libraryDependencies += "io.circe" %% "circe-core" % "0.14.2",
libraryDependencies += "io.circe" %% "circe-parser" % "0.14.2",
libraryDependencies += "co.fs2" %% "fs2-core" % "3.3.0",
libraryDependencies += "co.fs2" %% "fs2-reactive-streams" % "3.3.0",
libraryDependencies += "com.comcast" %% "ip4s-core" % "3.2.0",
libraryDependencies += "com.comcast" %% "ip4s-test-kit" % "3.2.0" % "test",
libraryDependencies += "com.datastax.oss" % "java-driver-core" % "4.15.0",
libraryDependencies += "com.dimafeng" %% "testcontainers-scala-scalatest" % "0.40.11" % "test",
libraryDependencies += "io.circe" %% "circe-core" % "0.14.3",
libraryDependencies += "io.circe" %% "circe-parser" % "0.14.3",
libraryDependencies += "org.http4s" %% "http4s-circe" % "1.0.0-M32",
libraryDependencies += "org.http4s" %% "http4s-core" % "1.0.0-M32",
libraryDependencies += "org.http4s" %% "http4s-dsl" % "1.0.0-M32",
libraryDependencies += "org.http4s" %% "http4s-ember-server" % "1.0.0-M32",
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.12" % "test",
libraryDependencies += "org.scalatestplus" %% "scalacheck-1-16" % "3.2.12.0" % "test",
libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.0" % "runtime",
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.14" % "test",
libraryDependencies += "org.scalatestplus" %% "scalacheck-1-16" % "3.2.14.0" % "test",
libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.3" % "runtime",
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.3.14",
libraryDependencies += "org.typelevel" %% "cats-effect-testing-scalatest" % "1.4.0" % "test",
libraryDependencies += "org.typelevel" %% "log4cats-slf4j" % "2.4.0"
libraryDependencies += "org.typelevel" %% "log4cats-slf4j" % "2.5.0"
)
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

services:
cassandra:
image: cassandra:4.0.4
image: cassandra:4.1
ports:
- 9042:9042
bandwhichd-server:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,18 @@ object Message {
} yield message

given Codec[Measurement.NetworkConfiguration] =
Codec.forProduct5(
Codec.forProduct6(
"machine_id",
"timestamp",
"maybe_os_release",
"hostname",
"interfaces",
"open_sockets"
)(Measurement.NetworkConfiguration.apply)(nc =>
(
nc.machineId,
nc.timing,
nc.maybeOsRelease,
nc.hostname,
nc.interfaces,
nc.openSockets
Expand Down Expand Up @@ -157,6 +159,9 @@ object Message {
given Decoder[InterfaceName] = Decoder[String].map(InterfaceName.apply)
given Encoder[MachineId] = Encoder[UUID].contramap(_.value)
given Decoder[MachineId] = Decoder[UUID].map(MachineId.apply)
given Encoder[OsRelease.FileContents] = Encoder[String].contramap(_.value)
given Decoder[OsRelease.FileContents] =
Decoder[String].map(OsRelease.FileContents.apply)
given Encoder[ProcessName] = Encoder[String].contramap(_.value)
given Decoder[ProcessName] = Decoder[String].map(ProcessName.apply)
given Encoder[Protocol] = Encoder[String].contramap(_ match
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,56 @@ import io.circe.{Encoder, Json}
object StatsCodecs {
val encoder: Encoder[MonitoredStats] =
(stats: MonitoredStats) =>
Json.obj(
"hosts" -> Json.fromFields(
stats.hosts
.map(monitoredHost =>
monitoredHost.hostId.uuid.toString -> Json.obj(
"hostname" -> Json.fromString(monitoredHost.hostname.toString),
"additional_hostnames" -> Json.fromValues(
monitoredHost.additionalHostnames.map(additionalHostname =>
Json.fromString(additionalHostname.toString)
)
),
"connections" -> stats
.connectionsFor(monitoredHost.hostId)
.fold(Json.obj())(hostIdsToConnections => {
Json.fromFields(
hostIdsToConnections.map[(String, Json)]((hostId, _) => {
hostId.uuid.toString -> Json.obj()
})
Json
.obj(
"hosts" -> Json.fromFields(
stats.hosts
.map(monitoredHost =>
monitoredHost.hostId.uuid.toString -> Json.obj(
"hostname" -> Json.fromString(
monitoredHost.hostname.toString
),
"os_release" -> monitoredHost.maybeOsRelease.fold(Json.Null)(
osRelease =>
Json.obj(
"pretty_name" -> osRelease.maybePrettyName
.fold(Json.Null)(prettyName =>
Json.fromString(prettyName.value)
),
"version_id" -> osRelease.maybeVersionId
.fold(Json.Null)(versionId =>
Json.fromString(versionId.value)
),
"id" -> osRelease.maybeId.fold(Json.Null)(id =>
Json.fromString(id.value)
)
)
),
"additional_hostnames" -> Json.fromValues(
monitoredHost.additionalHostnames.map(additionalHostname =>
Json.fromString(additionalHostname.toString)
)
})
),
"connections" -> stats
.connectionsFor(monitoredHost.hostId)
.fold(Json.obj())(hostIdsToConnections => {
Json.fromFields(
hostIdsToConnections
.map[(String, Json)]((hostId, _) => {
hostId.uuid.toString -> Json.obj()
})
)
})
)
)
)
),
"unmonitoredHosts" -> Json.fromFields(
stats.unidentifiedRemoteHosts.map(unidentifiedRemoteHost => {
unidentifiedRemoteHost.hostId.uuid.toString -> Json.obj(
"host" -> Json.fromString(unidentifiedRemoteHost.host.toString)
)
})
),
"unmonitoredHosts" -> Json.fromFields(
stats.unidentifiedRemoteHosts.map(unidentifiedRemoteHost => {
unidentifiedRemoteHost.hostId.uuid.toString -> Json.obj(
"host" -> Json.fromString(unidentifiedRemoteHost.host.toString)
)
})
)
)
)
.deepDropNullValues
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ class CassandraMigration[F[_]: Async](
private val cassandraContext: CassandraContext[F]
) {
def migrate(configuration: Configuration): F[Unit] =
for {
_ <- migrateV1(configuration)
_ <- migrateV2(configuration)
} yield ()

def migrateV1(configuration: Configuration): F[Unit] =
for {
_ <- createCidrType(configuration)
_ <- createMeasurementNetworkConfigurationInterfaceType(configuration)
Expand All @@ -18,6 +24,13 @@ class CassandraMigration[F[_]: Async](
_ <- createMeasurementsTable(configuration)
} yield ()

def migrateV2(configuration: Configuration): F[Unit] =
for {
_ <- addNetworkConfigurationMaybeOrReleaseToMeasurementsTable(
configuration
)
} yield ()

private def createMeasurementsTable(
configuration: Configuration
): F[Unit] =
Expand Down Expand Up @@ -111,4 +124,17 @@ class CassandraMigration[F[_]: Async](
.setTimeout(configuration.migrationQueryTimeout)
.build()
)

private def addNetworkConfigurationMaybeOrReleaseToMeasurementsTable(
configuration: Configuration
): F[Unit] =
cassandraContext.executeRawExpectNoRow(
SimpleStatement
.builder(
"alter table measurements_by_date add if not exists network_configuration_maybe_os_release text"
)
.setKeyspace(configuration.measurementsKeyspace)
.setTimeout(configuration.migrationQueryTimeout)
.build()
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import scala.util.Try

object MeasurementCassandraCodecs {
given Codec[Measurement[Timing]] =
Codec.forProduct9(
Codec.forProduct10(
"date",
"timestamp",
"end_timestamp",
"machine_id",
"measurement_type",
"network_configuration_maybe_os_release",
"network_configuration_hostname",
"network_configuration_interfaces",
"network_configuration_open_sockets",
Expand All @@ -34,6 +35,7 @@ object MeasurementCassandraCodecs {
endTimestamp: Timing.Timestamp,
machineId: MachineId,
measurementType: String,
maybeOsRelease: String,
hostname: Hostname,
interfaces: Seq[Interface],
openSockets: Seq[OpenSocket],
Expand All @@ -44,6 +46,7 @@ object MeasurementCassandraCodecs {
Measurement.NetworkConfiguration(
machineId = machineId,
timing = timestamp,
maybeOsRelease = Some(OsRelease.FileContents(maybeOsRelease)),
hostname = hostname,
interfaces = interfaces,
openSockets = openSockets
Expand All @@ -68,6 +71,7 @@ object MeasurementCassandraCodecs {
case Measurement.NetworkConfiguration(
machineId,
timing,
maybeOsRelease,
hostname,
interfaces,
openSockets
Expand All @@ -78,6 +82,7 @@ object MeasurementCassandraCodecs {
Timing.Timestamp(Instant.EPOCH),
machineId,
"network_configuration",
maybeOsRelease.fold("")(_.value),
hostname,
interfaces,
openSockets,
Expand All @@ -94,6 +99,7 @@ object MeasurementCassandraCodecs {
Timing.Timestamp(timing.value.normalizedStop),
machineId,
"network_utilization",
"",
Hostname.fromString("a").get,
Seq.empty[Interface],
Seq.empty[OpenSocket],
Expand Down
84 changes: 84 additions & 0 deletions src/main/scala/de/neuland/bandwhichd/server/domain/OsRelease.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package de.neuland.bandwhichd.server.domain

import scala.util.matching.Regex

case class OsRelease(
maybeId: Option[OsRelease.Id],
maybeVersionId: Option[OsRelease.VersionId],
maybePrettyName: Option[OsRelease.PrettyName]
)

object OsRelease {
def apply(fileContents: FileContents): OsRelease = {
import de.neuland.bandwhichd.server.domain.OsRelease.FileContents.findValue

OsRelease(
maybeId = fileContents.findValue("ID").map(Id.apply),
maybeVersionId =
fileContents.findValue("VERSION_ID").map(VersionId.apply),
maybePrettyName =
fileContents.findValue("PRETTY_NAME").map(PrettyName.apply)
)
}

opaque type FileContents = String

object FileContents {
def apply(value: String): FileContents = value

private val rowRegex =
"""^ *([a-zA-Z]+[a-zA-Z0-9_]*) *= *(?:"([^"]*)"|([a-zA-Z0-9]+)) *$""".r

extension (fileContents: FileContents) {
def value: String = fileContents

def parse: OsRelease = OsRelease.apply(fileContents)

def findValue(key: String): Option[String] =
fileContents.value
.split("\\n")
.to(LazyList)
.flatMap(rowRegex.findFirstMatchIn)
.flatMap(_ match
case Regex.Groups(foundKey, quotedValue, null)
if key.equalsIgnoreCase(foundKey) =>
Some(quotedValue)
case Regex.Groups(foundKey, null, unquotedValue)
if key.equalsIgnoreCase(foundKey) =>
Some(unquotedValue)
case _ => None
)
.headOption
}
}

opaque type Id = String

object Id {
def apply(value: String): Id = value

extension (id: Id) {
def value: String = id
}
}

opaque type VersionId = String

object VersionId {
def apply(value: String): VersionId = value

extension (versionId: VersionId) {
def value: String = versionId
}
}

opaque type PrettyName = String

object PrettyName {
def apply(value: String): PrettyName = value

extension (prettyName: PrettyName) {
def value: String = prettyName
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ object Measurement {
case class NetworkConfiguration(
machineId: MachineId,
timing: Timing.Timestamp,
maybeOsRelease: Option[OsRelease.FileContents],
hostname: Hostname,
interfaces: Seq[Interface],
openSockets: Seq[OpenSocket]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ object MachineIdHost {

case class MonitoredHost(
hostId: HostId.MachineId,
maybeOsRelease: Option[OsRelease],
hostname: Hostname,
additionalHostnames: Set[Hostname],
interfaces: Set[Interface]
Expand Down
Loading

0 comments on commit f4cf5a9

Please sign in to comment.