2018-05-22 19:43:36 +00:00
|
|
|
// Copyright 2016-2018, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// 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.
|
2017-02-13 22:26:46 +00:00
|
|
|
|
|
|
|
package graph
|
|
|
|
|
|
|
|
import (
|
2021-11-13 02:37:17 +00:00
|
|
|
"errors"
|
2017-02-13 22:26:46 +00:00
|
|
|
)
|
|
|
|
|
Begin resource modeling and planning
This change introduces a new package, pkg/resource, that will form
the foundation for actually performing deployment plans and applications.
It contains the following key abstractions:
* resource.Provider is a wrapper around the CRUD operations exposed by
underlying resource plugins. It will eventually defer to resource.Plugin,
which itself defers -- over an RPC interface -- to the actual plugin, one
per package exposing resources. The provider will also understand how to
load, cache, and overall manage the lifetime of each plugin.
* resource.Resource is the actual resource object. This is created from
the overall evaluation object graph, but is simplified. It contains only
serializable properties, for example. Inter-resource references are
translated into serializable monikers as part of creating the resource.
* resource.Moniker is a serializable string that uniquely identifies
a resource in the Mu system. This is in contrast to resource IDs, which
are generated by resource providers and generally opaque to the Mu
system. See marapongo/mu#69 for more information about monikers and some
of their challenges (namely, designing a stable algorithm).
* resource.Snapshot is a "snapshot" taken from a graph of resources. This
is a transitive closure of state representing one possible configuration
of a given environment. This is what plans are created from. Eventually,
two snapshots will be diffable, in order to perform incremental updates.
One way of thinking about this is that a snapshot of the old world's state
is advanced, one step at a time, until it reaches a desired snapshot of
the new world's state.
* resource.Plan is a plan for carrying out desired CRUD operations on a target
environment. Each plan consists of zero-to-many Steps, each of which has
a CRUD operation type, a resource target, and a next step. This is an
enumerator because it is possible the plan will evolve -- and introduce new
steps -- as it is carried out (hence, the Next() method). At the moment, this
is linearized; eventually, we want to make this more "graph-like" so that we
can exploit available parallelism within the dependencies.
There are tons of TODOs remaining. However, the `mu plan` command is functioning
with these new changes -- including colorization FTW -- so I'm landing it now.
This is part of marapongo/mu#38 and marapongo/mu#41.
2017-02-17 20:31:48 +00:00
|
|
|
// Topsort topologically sorts the graph, yielding an array of nodes that are in dependency order, using a simple
|
2017-02-13 22:26:46 +00:00
|
|
|
// DFS-based algorithm. The graph must be acyclic, otherwise this function will return an error.
|
Begin resource modeling and planning
This change introduces a new package, pkg/resource, that will form
the foundation for actually performing deployment plans and applications.
It contains the following key abstractions:
* resource.Provider is a wrapper around the CRUD operations exposed by
underlying resource plugins. It will eventually defer to resource.Plugin,
which itself defers -- over an RPC interface -- to the actual plugin, one
per package exposing resources. The provider will also understand how to
load, cache, and overall manage the lifetime of each plugin.
* resource.Resource is the actual resource object. This is created from
the overall evaluation object graph, but is simplified. It contains only
serializable properties, for example. Inter-resource references are
translated into serializable monikers as part of creating the resource.
* resource.Moniker is a serializable string that uniquely identifies
a resource in the Mu system. This is in contrast to resource IDs, which
are generated by resource providers and generally opaque to the Mu
system. See marapongo/mu#69 for more information about monikers and some
of their challenges (namely, designing a stable algorithm).
* resource.Snapshot is a "snapshot" taken from a graph of resources. This
is a transitive closure of state representing one possible configuration
of a given environment. This is what plans are created from. Eventually,
two snapshots will be diffable, in order to perform incremental updates.
One way of thinking about this is that a snapshot of the old world's state
is advanced, one step at a time, until it reaches a desired snapshot of
the new world's state.
* resource.Plan is a plan for carrying out desired CRUD operations on a target
environment. Each plan consists of zero-to-many Steps, each of which has
a CRUD operation type, a resource target, and a next step. This is an
enumerator because it is possible the plan will evolve -- and introduce new
steps -- as it is carried out (hence, the Next() method). At the moment, this
is linearized; eventually, we want to make this more "graph-like" so that we
can exploit available parallelism within the dependencies.
There are tons of TODOs remaining. However, the `mu plan` command is functioning
with these new changes -- including colorization FTW -- so I'm landing it now.
This is part of marapongo/mu#38 and marapongo/mu#41.
2017-02-17 20:31:48 +00:00
|
|
|
func Topsort(g Graph) ([]Vertex, error) {
|
2017-02-13 22:26:46 +00:00
|
|
|
var sorted []Vertex // will hold the sorted vertices.
|
2017-02-13 22:41:20 +00:00
|
|
|
visiting := make(map[Vertex]bool) // temporary entries to detect cycles.
|
|
|
|
visited := make(map[Vertex]bool) // entries to avoid visiting the same node twice.
|
2017-02-13 22:26:46 +00:00
|
|
|
|
|
|
|
// Now enumerate the roots, topologically sorting their dependencies.
|
|
|
|
roots := g.Roots()
|
|
|
|
for _, r := range roots {
|
2017-02-18 18:22:04 +00:00
|
|
|
if err := topvisit(r.To(), &sorted, visiting, visited); err != nil {
|
2017-02-13 22:26:46 +00:00
|
|
|
return sorted, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sorted, nil
|
|
|
|
}
|
|
|
|
|
2017-02-13 22:41:20 +00:00
|
|
|
func topvisit(n Vertex, sorted *[]Vertex, visiting map[Vertex]bool, visited map[Vertex]bool) error {
|
|
|
|
if visiting[n] {
|
2017-02-13 22:26:46 +00:00
|
|
|
// This is not a DAG! Stop sorting right away, and issue an error.
|
2017-06-06 01:11:51 +00:00
|
|
|
// IDEA: return diagnostic information about why this isn't a DAG (e.g., full cycle path).
|
2017-02-13 22:26:46 +00:00
|
|
|
return errors.New("Graph is not a DAG")
|
|
|
|
}
|
2017-02-13 22:41:20 +00:00
|
|
|
if !visited[n] {
|
|
|
|
visiting[n] = true
|
2017-02-13 22:26:46 +00:00
|
|
|
for _, m := range n.Outs() {
|
2017-02-13 22:41:20 +00:00
|
|
|
if err := topvisit(m.To(), sorted, visiting, visited); err != nil {
|
2017-02-13 22:26:46 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-02-13 22:41:20 +00:00
|
|
|
visited[n] = true
|
|
|
|
visiting[n] = false
|
2017-02-13 22:26:46 +00:00
|
|
|
*sorted = append(*sorted, n)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|