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

lson.core_2.11.0.9.71.source-code.CycleDetection.scala Maven / Gradle / Ivy

The newest version!
//: ----------------------------------------------------------------------------
//: Copyright (C) 2017 Verizon.  All Rights Reserved.
//:
//:   Licensed under the Apache License, Version 2.0 (the "License");
//:   you may not use this file except in compliance with the License.
//:   You may obtain a copy of the License at
//:
//:       http://www.apache.org/licenses/LICENSE-2.0
//:
//:   Unless required by applicable law or agreed to in writing, software
//:   distributed under the License is distributed on an "AS IS" BASIS,
//:   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//:   See the License for the specific language governing permissions and
//:   limitations under the License.
//:
//: ----------------------------------------------------------------------------
package nelson

import nelson.storage.{StoreOp, StoreOpF, run => runs}
import Manifest.{apply => _, _}
import nelson.Datacenter.DCUnit
import routing._

import scalaz._
import Scalaz._
import scalaz.concurrent.Task

object CycleDetection {
  type Valid[A] = ValidationNel[NelsonError, A]
  type ReverseRoutingGraph = RoutingGraph

  def validateNoCycles(
    m: Manifest,
    cfg: NelsonConfig): DisjunctionT[Task, NonEmptyList[NelsonError], Unit] = {
    val op: StoreOpF[Valid[Unit]] = detect(m, cfg)
    DisjunctionT(runs(cfg.storage, op).map(_.disjunction))
  }

  // Detecting definite cycles:
  // For each namespace:
  // - Get the associated routing graph.
  // - Reverse the graph to get the reachability graph.
  // - For each unit:
  //   - Find ids of all shiftable deployments (for now, the active feature versions).
  //   - Find reachability of shiftable deployments.
  //   - For each declared unit dep:
  //     - For each active deployment id that matches the service name.
  //       - If shiftable deployment id is in reachables:
  //         - Then NelsonError("Dependency cycle detected for $unit in $namespace: ${serviceName.name}:${serviceName.version}.
  //       - Else: Success
  def detect(
    m: Manifest,
    cfg: NelsonConfig): StoreOpF[Valid[Unit]] = {
    for {
      ns <- getNamespaces(cfg.datacenters, m.namespaces)
      ngs <- getNamespaceGraphs(ns)
      result <- ngs.traverse[StoreOpF, Valid[Unit]] {
        case (namespace, reachabilityGraph) =>
          validateUnitsInNamespace(m, namespace, reachabilityGraph)
      }
    } yield result.sequence_
  }

  def validateUnitsInNamespace(
    m: Manifest,
    namespace: Datacenter.Namespace,
    reachabilityGraph: ReverseRoutingGraph
  ): StoreOpF[Valid[Unit]] = {
    m.units.traverse[StoreOpF, Valid[Unit]](
      validateUnitDeps(namespace, reachabilityGraph)
    ).map(_.sequence_)
  }

  def getNamespaceGraphs(ns: List[Datacenter.Namespace])
  : StoreOpF[List[(Datacenter.Namespace, ReverseRoutingGraph)]] = {
    ns.traverse[StoreOpF, (Datacenter.Namespace, ReverseRoutingGraph)](n =>
      RoutingTable.routingGraph(n).map(g => (n, g.reverse)))
  }

  def getNamespaces(
    datacenters: List[Datacenter],
    namespaces: List[Manifest.Namespace]): StoreOpF[List[Datacenter.Namespace]] = {
    // These namespaces have been validated by the validations this validation depends on.
    // We effectively ignore the optionality of the storage with _.flatten.
    datacenters.traverseM[StoreOpF, Datacenter.Namespace] { dc =>
      namespaces.traverse[StoreOpF, Option[Datacenter.Namespace]] { n =>
        StoreOp.getNamespace(dc.name, n.name)
      }.map(_.flatten)
    }
  }

  def err(
    u: UnitDef,
    namespace: Datacenter.Namespace,
    dcu: DCUnit): NelsonError = CyclicDependency(
    s"Dependency cycle detected for unit '${u.name}' in namespace '${namespace.name.asString}' in datacenter '${namespace.datacenter}': ${dcu.name}@${dcu.version}"
  )

  def dependants(g: ReverseRoutingGraph)
    (d: Datacenter.Deployment): List[DCUnit] =
    g.reachable(RoutingNode(d)).map(_.deployment).collect {
      case Some(d) => d.unit
    }.toList

  def validateUnitDeps(
    namespace: Datacenter.Namespace,
    reachabilityGraph: ReverseRoutingGraph)
    (u: UnitDef): StoreOpF[Valid[Unit]] = {

    for {
    // Get all nodes whose dependants could potentially be routed to this unit.
      shiftables <- StoreOp.listShiftableDeployments(u, namespace.id)

      // Get all potential dependants.
      reachables = shiftables.flatMap(dependants(reachabilityGraph)).toSet.toList

      // Check whether unit's declared deps create a cycle.
      result = collectViolations(u, reachables, namespace)
    } yield result
  }

  def collectViolations(
    u: UnitDef,
    reachables: List[DCUnit],
    namespace: Datacenter.Namespace
  ): Valid[Unit] = {
    u.dependencies.toList.traverse_[Valid] {
      case (dep, depVersion) =>
        reachables
          .filter(isViolation(dep, depVersion))
          .traverse_[Valid] { dcu =>
          Validation.failureNel[NelsonError, Unit](err(u, namespace, dcu))
        }
    }
  }

  def isViolation(
    dep: String,
    depVersion: FeatureVersion)
    (reachable: DCUnit): Boolean = {
    reachable.name == dep && reachable.version.toFeatureVersion == depVersion
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy