Skip to content

Commit

Permalink
feat: controller runtime implementation
Browse files Browse the repository at this point in the history
This enhances Resource and State implementation, and build initial
Controller and Controller Runtime implementation. Code is reasonably
covered with tunit-tests.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
  • Loading branch information
smira authored and talos-bot committed Jan 13, 2021
1 parent f450ab7 commit 53fb919
Show file tree
Hide file tree
Showing 56 changed files with 3,988 additions and 198 deletions.
5 changes: 4 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
#
# Generated on 2020-08-28T20:43:11Z by kres 292ed36-dirty.
# Generated on 2020-12-30T20:27:21Z by kres latest.


# options for analysis running
Expand Down Expand Up @@ -125,6 +125,9 @@ linters:
- gomnd
- goerr113
- nestif
- wrapcheck
- paralleltest
- exhaustivestruct
disable-all: false
fast: false

Expand Down
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# syntax = docker/dockerfile-upstream:1.1.7-experimental
# syntax = docker/dockerfile-upstream:1.2.0-labs

# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
#
# Generated on 2020-08-28T20:50:10Z by kres 292ed36-dirty.
# Generated on 2020-12-30T20:27:21Z by kres latest.

ARG TOOLCHAIN

Expand All @@ -24,7 +24,7 @@ FROM toolchain AS tools
ENV GO111MODULE on
ENV CGO_ENABLED 0
ENV GOPATH /go
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b /bin v1.30.0
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b /bin v1.33.0
ARG GOFUMPT_VERSION
RUN cd $(mktemp -d) \
&& go mod init tmp \
Expand Down
63 changes: 41 additions & 22 deletions cmd/directory-fun/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import (

"github.com/talos-systems/os-runtime/pkg/resource"
"github.com/talos-systems/os-runtime/pkg/state"
"github.com/talos-systems/os-runtime/pkg/state/impl/local"
"github.com/talos-systems/os-runtime/pkg/state/impl/inmem"
)

// DirectoryTask implements simple process attached to the state.
//
// DirectoryTask attempts to create path when the parent path got created.
// DirectoryTask watches for parent to be torn down, starts tear down, waits for children
// DirectoryTask watches for parent to be torn down, starts tear down process, waits for children
// to be destroyed, and removes the path.
//
// DirectoryTask is a model of task in some OS sequencer.
Expand All @@ -34,7 +34,9 @@ func DirectoryTask(world state.State, path string) {
err error
)

if parent, err = world.WatchFor(ctx, resource.NewMetadata(defaultNs, PathResourceType, base, resource.VersionUndefined), state.WithEventTypes(state.Created, state.Updated)); err != nil {
if parent, err = world.WatchFor(ctx,
resource.NewMetadata(defaultNs, PathResourceType, base, resource.VersionUndefined),
state.WithEventTypes(state.Created, state.Updated)); err != nil {
log.Fatal(err)
}

Expand All @@ -52,38 +54,39 @@ func DirectoryTask(world state.State, path string) {

log.Printf("%q: created %q", path, path)

if parent, err = world.UpdateWithConflicts(ctx, parent, func(r resource.Resource) error {
r.(*PathResource).AddDependent(self)
if err = world.AddFinalizer(ctx, parent.Metadata(), self.String()); err != nil {
log.Fatal(err)
}

return nil
}); err != nil {
parent, err = world.Get(ctx, parent.Metadata())
if err != nil {
log.Fatal(err)
}

log.Printf("%q: %q.dependents = %q", path, parent.Metadata().ID(), parent.(*PathResource).spec.dependents)
log.Printf("%q: parent %q finalizers are %q", path, base, parent.Metadata().Finalizers())

// doing something useful here <>

log.Printf("%q: watching for teardown %q", path, base)

if parent, err = world.WatchFor(ctx, resource.NewMetadata(defaultNs, PathResourceType, base, resource.VersionUndefined), state.WithEventTypes(state.Destroyed, state.Torndown)); err != nil {
if parent, err = world.WatchFor(ctx,
parent.Metadata(),
state.WithEventTypes(state.Created, state.Updated),
state.WithPhases(resource.PhaseTearingDown)); err != nil {
log.Fatal(err)
}

log.Printf("%q: teardown self", path)

if err = world.Teardown(ctx, self.Metadata()); err != nil {
if _, err = world.Teardown(ctx, self.Metadata()); err != nil {
log.Fatal(err)
}

log.Printf("%q: watching for dependents to vanish %q", path, path)

if _, err = world.WatchFor(ctx,
resource.NewMetadata(defaultNs, PathResourceType, path, resource.VersionUndefined),
state.WithEventTypes(state.Created, state.Updated, state.Torndown),
state.WithCondition(func(r resource.Resource) (bool, error) {
return len(r.(*PathResource).spec.dependents) == 0, nil
})); err != nil {
self.Metadata(),
state.WithFinalizerEmpty()); err != nil {
log.Fatal(err)
}

Expand All @@ -93,14 +96,17 @@ func DirectoryTask(world state.State, path string) {
log.Fatal(err)
}

if _, err = world.UpdateWithConflicts(ctx, parent, func(r resource.Resource) error {
r.(*PathResource).DropDependent(self)
if err = world.RemoveFinalizer(ctx, parent.Metadata(), self.String()); err != nil {
log.Fatal(err)
}

return nil
}); err != nil {
parent, err = world.Get(ctx, parent.Metadata())
if err != nil {
log.Fatal(err)
}

log.Printf("%q: parent %q finalizers are %q", path, base, parent.Metadata().Finalizers())

if err = world.Destroy(ctx, self.Metadata()); err != nil {
log.Fatal(err)
}
Expand All @@ -110,7 +116,7 @@ const defaultNs = "default"

func main() {
ctx := context.Background()
world := state.WrapCore(local.NewState(defaultNs))
world := state.WrapCore(inmem.NewState(defaultNs))

root := NewPathResource(defaultNs, ".")
if err := world.Create(ctx, root); err != nil {
Expand All @@ -137,9 +143,22 @@ func main() {

time.Sleep(2 * time.Second)

if err := world.Teardown(ctx, root.Metadata()); err != nil {
if _, err := world.Teardown(ctx, root.Metadata()); err != nil {
log.Fatal(err)
}

time.Sleep(10 * time.Second)
if _, err := world.WatchFor(ctx,
root.Metadata(),
state.WithFinalizerEmpty()); err != nil {
log.Fatal(err)
}

rootRes, err := world.Get(ctx, root.Metadata())
if err != nil {
log.Fatal(err)
}

if err := world.Destroy(ctx, rootRes.Metadata()); err != nil {
log.Fatal(err)
}
}
35 changes: 5 additions & 30 deletions cmd/directory-fun/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,12 @@ import (

const PathResourceType = resource.Type("os/path")

type pathResourceSpec struct {
dependents []string
}

// PathResource represents a path in the filesystem.
//
// Resource ID is the path, and dependents are all the immediate
// children on the path.
type PathResource struct {
md resource.Metadata
spec pathResourceSpec
md resource.Metadata
}

func NewPathResource(ns resource.Namespace, path string) *PathResource {
Expand All @@ -35,40 +30,20 @@ func NewPathResource(ns resource.Namespace, path string) *PathResource {
return r
}

func (path *PathResource) Metadata() resource.Metadata {
return path.md
func (path *PathResource) Metadata() *resource.Metadata {
return &path.md
}

func (path *PathResource) Spec() interface{} {
return path.spec
return nil
}

func (path *PathResource) String() string {
return fmt.Sprintf("PathResource(%q)", path.md.ID())
}

func (path *PathResource) Copy() resource.Resource {
func (path *PathResource) DeepCopy() resource.Resource {
return &PathResource{
md: path.md,
spec: pathResourceSpec{
dependents: append([]string(nil), path.spec.dependents...),
},
}
}

func (path *PathResource) AddDependent(dependent *PathResource) {
path.spec.dependents = append(path.spec.dependents, dependent.md.ID())
path.md.BumpVersion()
}

func (path *PathResource) DropDependent(dependent *PathResource) {
for i, p := range path.spec.dependents {
if p == dependent.md.ID() {
path.spec.dependents = append(path.spec.dependents[:i], path.spec.dependents[i+1:]...)

break
}
}

path.md.BumpVersion()
}
11 changes: 10 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,13 @@ module github.com/talos-systems/os-runtime

go 1.14

require github.com/stretchr/testify v1.6.1
require (
github.com/AlekSi/pointer v1.1.0
github.com/hashicorp/go-memdb v1.3.0
github.com/hashicorp/go-multierror v1.1.0
github.com/stretchr/testify v1.6.1
github.com/talos-systems/go-retry v0.2.0
go.uber.org/goleak v1.1.10
golang.org/x/sync v0.0.0-20190423024810-112230192c58
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
)
41 changes: 41 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,52 @@
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE=
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU=
github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/talos-systems/go-retry v0.2.0 h1:YpQHmtTZ2k0i/bBYRIasdVmF0XaiISVJUOrmZ6FzgLU=
github.com/talos-systems/go-retry v0.2.0/go.mod h1:HiXQqyVStZ35uSY/MTLWVvQVmC3lIW2MS5VdDaMtoKM=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
21 changes: 21 additions & 0 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package controller defines common interfaces to be implemented by the controllers and controller runtime.
package controller

import (
"context"
"log"

"github.com/talos-systems/os-runtime/pkg/resource"
)

// Controller interface should be implemented by Controllers.
type Controller interface {
Name() string
ManagedResources() (resource.Namespace, resource.Type)

Run(context.Context, Runtime, *log.Logger) error
}
69 changes: 69 additions & 0 deletions pkg/controller/runtime.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package controller

import (
"context"

"github.com/talos-systems/os-runtime/pkg/resource"
"github.com/talos-systems/os-runtime/pkg/state"
)

// ReconcileEvent is a signal for controller to reconcile its resources.
type ReconcileEvent struct{}

// Runtime interface as presented to the controller.
type Runtime interface {
EventCh() <-chan ReconcileEvent
QueueReconcile()

UpdateDependencies([]Dependency) error

Reader
Writer
}

// DependencyKind for dependencies.
type DependencyKind = int

// Dependency kinds.
const (
DependencyWeak int = iota
DependencyHard
)

// Dependency of controller on some resource(s).
//
// Each controller might have multiple dependencies, it might depend on
// all the objects of some type under namespace, or on specific object by ID.
//
// Dependency might be either Weak or Hard. Any kind of dependency triggers
// cascading reconcile on changes, hard dependencies in addition block deletion of
// parent object until all the dependencies are torn down.
type Dependency struct {
Namespace resource.Namespace
Type resource.Type
ID *resource.ID
Kind DependencyKind
}

// Reader provides read-only access to the state.
type Reader interface {
Get(context.Context, resource.Pointer) (resource.Resource, error)
List(context.Context, resource.Kind) (resource.List, error)
WatchFor(context.Context, resource.Pointer, ...state.WatchForConditionFunc) (resource.Resource, error)
}

// Writer provides write access to the state.
//
// Only managed objects can be written to by the controller.
type Writer interface {
Update(context.Context, resource.Resource, func(resource.Resource) error) error
Teardown(context.Context, resource.Pointer) (bool, error)
Destroy(context.Context, resource.Pointer) error

AddFinalizer(context.Context, resource.Pointer, ...resource.Finalizer) error
RemoveFinalizer(context.Context, resource.Pointer, ...resource.Finalizer) error
}
Loading

0 comments on commit 53fb919

Please sign in to comment.