All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pt.tecnico.dsi.openstack.neutron.models.Network.scala Maven / Gradle / Ivy

The newest version!
package pt.tecnico.dsi.openstack.neutron.models

import java.time.OffsetDateTime
import cats.{Monad, Parallel, derived}
import cats.derived.ShowPretty
import com.comcast.ip4s.IpAddress
import io.circe.derivation.{deriveDecoder, deriveEncoder, renaming}
import io.circe.{Decoder, Encoder, Json}
import io.chrisdavenport.cats.time.offsetdatetimeInstances
import pt.tecnico.dsi.openstack.common.models.{Identifiable, Link}
import pt.tecnico.dsi.openstack.neutron.NeutronClient
import pt.tecnico.dsi.openstack.neutron.models.Network.Segment

object Network {
  object Segment {
    val providerRenames: Map[String, String] = Map(
      "type" -> "provider:network_type",
      "physicalNetwork" -> "provider:physical_network",
      "segmentationId" -> "provider:segmentation_id",
    ).withDefault(renaming.snakeCase)
    implicit val decoder: Decoder[Segment] = deriveDecoder(providerRenames)
    implicit val encoder: Encoder[Segment] = deriveEncoder(providerRenames)
    
    implicit val show: ShowPretty[Segment] = derived.semiauto.showPretty
  }
  case class Segment(`type`: String, physicalNetwork: Option[String] = None, segmentationId: Option[Int] = None)
  
  private val baseRenames: Map[String, String] = Map(
    "routerExternal" -> "router:external",
    "revision" -> "revision_number",
    "subnetIds" -> "subnets",
  ).withDefault(renaming.snakeCase)
  
  private def encoderHandleSegments[T](derived: Encoder.AsObject[T], getSegments: T => Option[List[Segment]]): Encoder.AsObject[T] = (t: T) => {
    if (getSegments(t).map(_.size).getOrElse(0) == 1) {
      // When the number of segments is 1 Openstack makes it special 
      val obj = derived.encodeObject(t)
      val firstSegmentEntries: Option[Iterable[(String, Json)]] = for {
        segmentsJson <- obj("segments")
        segments <- segmentsJson.asArray
        firstSegment <- segments.headOption
        firstSegmentObj <- firstSegment.asObject
      } yield firstSegmentObj.toIterable
      firstSegmentEntries.getOrElse(Iterable.empty).foldLeft(obj.remove("segments")) {
        case (acc, field) => field +: acc
      }
    } else {
      // The derived encoder already does the right thing.
      derived.encodeObject(t)
    }
  }
  
  object Create {
    implicit val encoder: Encoder.AsObject[Create] = encoderHandleSegments(deriveEncoder[Create](baseRenames), _.segments)
    implicit val show: ShowPretty[Create] = derived.semiauto.showPretty
  }
  case class Create(
    name: String,
    description: String = "",
    mtu: Option[Int] = None,
    dnsDomain: Option[String] = None,
    segments: Option[List[Segment]] = None,
    adminStateUp: Option[Boolean] = None,
    portSecurityEnabled: Option[Boolean] = None,
    routerExternal: Option[Boolean] = None,
    shared: Option[Boolean] = None,
    isDefault: Option[Boolean] = None,
    availabilityZoneHints: Option[List[String]] = None,
    projectId: Option[String] = None,
  )

  object Update {
    implicit val encoder: Encoder[Update] = encoderHandleSegments(deriveEncoder[Update](baseRenames), _.segments)
    implicit val show: ShowPretty[Update] = derived.semiauto.showPretty
  }
  sealed case class Update(
    name: Option[String] = None,
    description: Option[String] = None,
    mtu: Option[Int] = None,
    dnsDomain: Option[String] = None,
    // Most Networking plug-ins (e.g. ML2 Plugin) and drivers do not support updating any provider related attributes.
    // This is here just for the plugins/drivers that allow it.
    segments: Option[List[Segment]] = None,
    adminStateUp: Option[Boolean] = None,
    portSecurityEnabled: Option[Boolean] = None,
    routerExternal: Option[Boolean] = None,
    shared: Option[Boolean] = None,
    isDefault: Option[Boolean] = None,
  ) {
    lazy val needsUpdate: Boolean = {
      // We could implement this with the next line, but that implementation is less reliable if the fields of this class change
      //  productIterator.asInstanceOf[Iterator[Option[Any]]].exists(_.isDefined)
      List(name, description, mtu, dnsDomain, segments, adminStateUp, portSecurityEnabled, routerExternal, shared, isDefault).exists(_.isDefined)
    }
  }
  
  implicit val decoder: Decoder[Network] = deriveDecoder[Network](baseRenames).prepare(_.withFocus(_.mapObject{ obj =>
    /* Openstack is inconsistent. When the network has multiple segment mappings the Json looks like this: {
      "network": {
        "segments": [
          {
            "provider:network_type": "vlan",
            "provider:physical_network": "public",
            "provider:segmentation_id": 2
          },
          {
            "provider:network_type": "flat",
            "provider:physical_network": "default",
            "provider:segmentation_id": 0
          }
        ],
        (...)
      }
    }
    However when it has single segment mapping the Json looks like this: {
      "network": {
        "provider:network_type": "vlan",
        "provider:physical_network": "public",
        "provider:segmentation_id": 2
        (...)
      }
    }
    Aka the fields of the Segment are directly inside the network object, instead of being inside an JsonObject inside segments field.
    */
    if (obj("segments").isDefined) {
      obj
    } else {
      val segmentSettings = for {
        field <- Segment.providerRenames.values
        value <- obj(field)
      } yield field -> value
      obj.add("segments", Json.arr(Json.fromFields(segmentSettings)))
    }
  }))
  implicit val encoder: Encoder[Network] = deriveEncoder(baseRenames)
  
  implicit val show: ShowPretty[Network] = derived.semiauto.showPretty
}
sealed case class Network(
  id: String,
  name: String,
  description: String,
  projectId: String,
  
  adminStateUp: Boolean,
  status: String, // ACTIVE, DOWN, BUILD or ERROR.
  mtu: Int,
  dnsDomain: Option[String] = None, // Cannot be ip4s Hostname because it ends with '.'
  subnetIds: List[String],
  ipv4AddressScope: Option[String],
  ipv6AddressScope: Option[String],
  segments: List[Segment] = List.empty,
  portSecurityEnabled: Boolean,
  routerExternal: Boolean = false,
  shared: Boolean,
  isDefault: Boolean = false, // missing also
  availabilityZoneHints: List[String] = List.empty,
  availabilityZones: List[String] = List.empty,
  
  revision: Int,
  createdAt: OffsetDateTime,
  updatedAt: OffsetDateTime,
  tags: List[String] = List.empty,
  links: List[Link] = List.empty
) extends Identifiable {
  def subnets[F[_]: Monad: Parallel](implicit neutron: NeutronClient[F]): F[List[Subnet[IpAddress]]] = {
    import cats.implicits._
    subnetIds.parTraverse(subnetId => neutron.subnets(subnetId))
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy