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

pt.tecnico.dsi.openstack.neutron.services.Networks.scala Maven / Gradle / Ivy

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

import cats.effect.Concurrent
import cats.syntax.flatMap._
import cats.syntax.functor._
import io.circe.Decoder
import org.http4s.Status.Conflict
import org.http4s.client.Client
import org.http4s.{Header, Uri}
import org.log4s.getLogger
import pt.tecnico.dsi.openstack.common.services.CrudService
import pt.tecnico.dsi.openstack.keystone.models.Session
import pt.tecnico.dsi.openstack.neutron.models.{Network, NeutronError}

final class Networks[F[_]: Concurrent: Client](baseUri: Uri, session: Session)
  extends CrudService[F, Network, Network.Create, Network.Update](baseUri, "network", session.authToken)
    with BulkCreate[F, Network, Network.Create] {
  
  override def defaultResolveConflict(existing: Network, create: Network.Create, keepExistingElements: Boolean, extraHeaders: Seq[Header.ToRaw]): F[Network] = {
    val updated = Network.Update(
      description = Option(create.description).filter(_ != existing.description),
      mtu = create.mtu.filter(_ != existing.mtu),
      dnsDomain = if (create.dnsDomain != existing.dnsDomain) create.dnsDomain else None,
      // Most Networking plug-ins (e.g. ML2 Plugin) and drivers do not support updating any provider related attributes.
      // The openstack we are testing against doesn't allow it. That is why we are not setting the segments.
      adminStateUp = create.adminStateUp.filter(_ != existing.adminStateUp),
      portSecurityEnabled = create.portSecurityEnabled.filter(_ != existing.portSecurityEnabled),
      routerExternal = create.routerExternal.filter(_ != existing.routerExternal),
      shared = create.shared.filter(_ != existing.shared),
      isDefault = create.isDefault.filter(_ != existing.isDefault),
    )
    if (updated.needsUpdate) update(existing.id, updated, extraHeaders:_*)
    else Concurrent[F].pure(existing)
  }
  override def createOrUpdate(create: Network.Create, keepExistingElements: Boolean = true, extraHeaders: Seq[Header.ToRaw] = Seq.empty)
    (resolveConflict: (Network, Network.Create) => F[Network] = defaultResolveConflict(_, _, keepExistingElements, extraHeaders)): F[Network] = {
    // If you ask openstack to create two networks with the same name it won't complain. We want to make the name unique **within** a project.
    create.projectId orElse session.scopedProjectId match {
      case None => super.create(create, extraHeaders:_*)
      case Some(projectId) =>
        list("name" -> create.name, "project_id" -> projectId, "limit" -> "2").flatMap {
          case Nil => super.create(create, extraHeaders:_*)
          case List(existing) =>
            getLogger.info(s"createOrUpdate: found unique $name (id: ${existing.id}) with the correct name and projectId.")
            resolveConflict(existing, create)
          case _ =>
            val message =
              s"""Cannot create a $name idempotently because more than one exists with:
                 |name: ${create.name}
                 |project: ${create.projectId}""".stripMargin
            Concurrent[F].raiseError(NeutronError(Conflict.reason, message))
        }
    }
  }
  
  /** @return an unsorted list of all the segmentation ids currently in use. This is usually a slow operation. */
  val listSegmentationIds: F[List[Int]] = {
    implicit val decoderInt: Decoder[Option[Int]] = Decoder.decodeOption[Int].at("provider:segmentation_id")
    super.list[Option[Int]](pluralName, uri.withQueryParam("fields", "provider:segmentation_id")).map(_.flatten)
  }
  
  /** @return the first available segmentation id that is within `begin` <= `id` <= `end`. This is usually a slow operation. */
  def firstAvailableSegmentationId(begin: Int, end: Int): F[Option[Int]] = listSegmentationIds.map { ids =>
    val filteredAndSortedIds = ids.filter(i => i >= begin && i <= end).sorted
    // First try to find a gap between the existing ids
    filteredAndSortedIds.sliding(2).collectFirst {
      case a :: b :: Nil if b - a > 1 => a + 1
      case a :: Nil => a + 1
    }.filter(_ <= end).orElse {
      // if the last element is less than `end` we can return the next element
      filteredAndSortedIds.lastOption.filter(_ < end).map(_ + 1)
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy