From f450ab759f4800f2ee8c65319f3bd323c41ec196 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Tue, 17 Nov 2020 23:11:56 +0300 Subject: [PATCH] feat: implement namespaces, clean up context use More of general cleanup/refactoring, no new features. Signed-off-by: Andrey Smirnov --- .conform.yaml | 34 +++++++++++++++ .drone.yml | 3 +- Makefile | 12 +++--- cmd/directory-fun/main.go | 42 +++++++++++------- cmd/directory-fun/path.go | 56 ++++++++++++------------ hack/git-chglog/config.yaml | 4 +- hack/release.sh | 4 +- pkg/resource/metadata.go | 66 +++++++++++++++++++++++++++++ pkg/resource/null.go | 49 --------------------- pkg/resource/pointer.go | 12 ++++++ pkg/resource/reference.go | 15 +++++++ pkg/resource/resource.go | 40 ++++++++++++----- pkg/resource/resource_test.go | 12 +++++- pkg/resource/tombstone.go | 59 ++++++++++++++++++++++++++ pkg/state/condition.go | 4 ++ pkg/state/errors.go | 4 ++ pkg/state/impl/local/collection.go | 52 ++++++++++++++--------- pkg/state/impl/local/errors.go | 12 ++++-- pkg/state/impl/local/errors_test.go | 11 +++-- pkg/state/impl/local/local.go | 37 +++++++++------- pkg/state/impl/local/local_test.go | 4 ++ pkg/state/options.go | 41 ++++++++++++++++++ pkg/state/state.go | 27 ++++++++---- pkg/state/wrap.go | 16 ++++--- 24 files changed, 444 insertions(+), 172 deletions(-) create mode 100644 .conform.yaml create mode 100644 pkg/resource/metadata.go delete mode 100644 pkg/resource/null.go create mode 100644 pkg/resource/pointer.go create mode 100644 pkg/resource/reference.go create mode 100644 pkg/resource/tombstone.go create mode 100644 pkg/state/options.go diff --git a/.conform.yaml b/.conform.yaml new file mode 100644 index 00000000..42ca95ea --- /dev/null +++ b/.conform.yaml @@ -0,0 +1,34 @@ +# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. +# +# Generated on 2020-09-25T18:15:38Z by kres ce6bee5-dirty. + +policies: +- type: commit + spec: + dco: true + gpg: false + spellcheck: + locale: US + maximumOfOneCommit: true + header: + length: 89 + imperative: true + case: lower + invalidLastCharacters: . + body: + required: true + conventional: + types: ["chore","docs","perf","refactor","style","test","release"] + scopes: ["*"] +- type: license + spec: + skipPaths: + - .git/ + includeSuffixes: + - .go + excludeSuffixes: + - .pb.go + header: | + // 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/. diff --git a/.drone.yml b/.drone.yml index d4998a68..574c214e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,7 +1,7 @@ --- # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2020-08-28T20:50:38Z by kres 292ed36-dirty. +# Generated on 2020-11-17T20:11:41Z by kres eb337ab. kind: pipeline type: kubernetes @@ -166,7 +166,6 @@ services: - --dns=8.8.4.4 - --mtu=1500 - --log-level=error - - --insecure-registry=http://registry.ci.svc:5000 privileged: true volumes: - name: outer-docker-socket diff --git a/Makefile b/Makefile index c4539ae2..ecc126a0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2020-08-28T20:51:32Z by kres 292ed36-dirty. +# Generated on 2020-09-25T18:15:38Z by kres ce6bee5-dirty. # common variables @@ -8,13 +8,13 @@ SHA := $(shell git describe --match=none --always --abbrev=8 --dirty) TAG := $(shell git describe --tag --always --dirty) BRANCH := $(shell git rev-parse --abbrev-ref HEAD) ARTIFACTS := _out -REGISTRY ?= docker.io -USERNAME ?= autonomy +REGISTRY ?= ghcr.io +USERNAME ?= talos-systems REGISTRY_AND_USERNAME ?= $(REGISTRY)/$(USERNAME) GOFUMPT_VERSION ?= abc0db2c416aca0f60ea33c23c76665f6e7ba0b6 GO_VERSION ?= 1.14 TESTPKGS ?= ./... -KRES_IMAGE ?= autonomy/kres:latest +KRES_IMAGE ?= ghcr.io/talos-systems/kres:latest # docker build settings @@ -34,7 +34,7 @@ COMMON_ARGS += --build-arg=USERNAME=$(USERNAME) COMMON_ARGS += --build-arg=TOOLCHAIN=$(TOOLCHAIN) COMMON_ARGS += --build-arg=GOFUMPT_VERSION=$(GOFUMPT_VERSION) COMMON_ARGS += --build-arg=TESTPKGS=$(TESTPKGS) -TOOLCHAIN ?= docker.io/golang:1.14-alpine +TOOLCHAIN ?= docker.io/golang:1.15-alpine # help menu @@ -130,7 +130,7 @@ lint: lint-golangci-lint lint-gofumpt lint-markdown ## Run all linters for the .PHONY: rekres rekres: @docker pull $(KRES_IMAGE) - @docker run --rm -v $(PWD):/src -w /src $(KRES_IMAGE) + @docker run --rm -v $(PWD):/src -w /src -e GITHUB_TOKEN $(KRES_IMAGE) .PHONY: help help: ## This help menu. diff --git a/cmd/directory-fun/main.go b/cmd/directory-fun/main.go index dc45dad2..e69f3a78 100644 --- a/cmd/directory-fun/main.go +++ b/cmd/directory-fun/main.go @@ -1,3 +1,7 @@ +// 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 main import ( @@ -30,7 +34,7 @@ func DirectoryTask(world state.State, path string) { err error ) - if parent, err = world.WatchFor(ctx, PathResourceType, base, 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) } @@ -40,15 +44,15 @@ func DirectoryTask(world state.State, path string) { log.Fatal(err) } - self := NewPathResource(path) + self := NewPathResource(defaultNs, path) - if err = world.Create(self); err != nil { + if err = world.Create(ctx, self); err != nil { log.Fatal(err) } log.Printf("%q: created %q", path, path) - if parent, err = world.UpdateWithConflicts(parent, func(r resource.Resource) error { + if parent, err = world.UpdateWithConflicts(ctx, parent, func(r resource.Resource) error { r.(*PathResource).AddDependent(self) return nil @@ -56,27 +60,30 @@ func DirectoryTask(world state.State, path string) { log.Fatal(err) } - log.Printf("%q: %q.dependents = %q", path, parent.ID(), parent.(*PathResource).dependents) + log.Printf("%q: %q.dependents = %q", path, parent.Metadata().ID(), parent.(*PathResource).spec.dependents) // doing something useful here <> log.Printf("%q: watching for teardown %q", path, base) - if parent, err = world.WatchFor(ctx, PathResourceType, base, state.WithEventTypes(state.Destroyed, state.Torndown)); err != nil { + if parent, err = world.WatchFor(ctx, resource.NewMetadata(defaultNs, PathResourceType, base, resource.VersionUndefined), state.WithEventTypes(state.Destroyed, state.Torndown)); err != nil { log.Fatal(err) } log.Printf("%q: teardown self", path) - if err = world.Teardown(self); 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, PathResourceType, path, state.WithEventTypes(state.Created, state.Updated, state.Torndown), state.WithCondition(func(r resource.Resource) (bool, error) { - return len(r.(*PathResource).dependents) == 0, nil - })); err != nil { + 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 { log.Fatal(err) } @@ -86,7 +93,7 @@ func DirectoryTask(world state.State, path string) { log.Fatal(err) } - if _, err = world.UpdateWithConflicts(parent, func(r resource.Resource) error { + if _, err = world.UpdateWithConflicts(ctx, parent, func(r resource.Resource) error { r.(*PathResource).DropDependent(self) return nil @@ -94,16 +101,19 @@ func DirectoryTask(world state.State, path string) { log.Fatal(err) } - if err = world.Destroy(self); err != nil { + if err = world.Destroy(ctx, self.Metadata()); err != nil { log.Fatal(err) } } +const defaultNs = "default" + func main() { - world := state.WrapCore(local.NewState()) + ctx := context.Background() + world := state.WrapCore(local.NewState(defaultNs)) - root := NewPathResource(".") - if err := world.Create(root); err != nil { + root := NewPathResource(defaultNs, ".") + if err := world.Create(ctx, root); err != nil { log.Fatal(err) } @@ -127,7 +137,7 @@ func main() { time.Sleep(2 * time.Second) - if err := world.Teardown(root); err != nil { + if err := world.Teardown(ctx, root.Metadata()); err != nil { log.Fatal(err) } diff --git a/cmd/directory-fun/path.go b/cmd/directory-fun/path.go index 15df3aa8..5298f9c5 100644 --- a/cmd/directory-fun/path.go +++ b/cmd/directory-fun/path.go @@ -1,70 +1,74 @@ +// 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/. + //nolint: golint package main import ( "fmt" - "strconv" "github.com/talos-systems/os-runtime/pkg/resource" ) 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 { - path string - version int - dependents []string + md resource.Metadata + spec pathResourceSpec } -func NewPathResource(path string) *PathResource { - return &PathResource{path: path} -} - -func (path *PathResource) ID() resource.ID { - return path.path -} +func NewPathResource(ns resource.Namespace, path string) *PathResource { + r := &PathResource{ + md: resource.NewMetadata(ns, PathResourceType, path, resource.VersionUndefined), + } + r.md.BumpVersion() -func (path *PathResource) Type() resource.Type { - return PathResourceType + return r } -func (path *PathResource) Version() resource.Version { - return strconv.Itoa(path.version) +func (path *PathResource) Metadata() resource.Metadata { + return path.md } func (path *PathResource) Spec() interface{} { - return nil + return path.spec } func (path *PathResource) String() string { - return fmt.Sprintf("PathResource(%q)", path.path) + return fmt.Sprintf("PathResource(%q)", path.md.ID()) } func (path *PathResource) Copy() resource.Resource { return &PathResource{ - path: path.path, - version: path.version, - dependents: append([]string(nil), path.dependents...), + md: path.md, + spec: pathResourceSpec{ + dependents: append([]string(nil), path.spec.dependents...), + }, } } func (path *PathResource) AddDependent(dependent *PathResource) { - path.dependents = append(path.dependents, dependent.path) - path.version++ + path.spec.dependents = append(path.spec.dependents, dependent.md.ID()) + path.md.BumpVersion() } func (path *PathResource) DropDependent(dependent *PathResource) { - for i, p := range path.dependents { - if p == dependent.path { - path.dependents = append(path.dependents[:i], path.dependents[i+1:]...) + 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.version++ + path.md.BumpVersion() } diff --git a/hack/git-chglog/config.yaml b/hack/git-chglog/config.yaml index ca4666e6..c6cbeca7 100644 --- a/hack/git-chglog/config.yaml +++ b/hack/git-chglog/config.yaml @@ -1,12 +1,12 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2020-08-28T20:43:11Z by kres 292ed36-dirty. +# Generated on 2020-09-25T18:15:38Z by kres ce6bee5-dirty. style: github template: CHANGELOG.tpl.md info: title: CHANGELOG - repository_url: https://github.com/talos-systems/talos + repository_url: https://github.com/talos-systems/os-runtime options: commits: # filters: diff --git a/hack/release.sh b/hack/release.sh index afec12b1..5bc464c7 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -2,14 +2,14 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2020-08-28T20:43:11Z by kres 292ed36-dirty. +# Generated on 2020-09-25T18:15:38Z by kres ce6bee5-dirty. set -e function changelog { if [ "$#" -eq 1 ]; then - git-chglog --output CHANGELOG.md -c ./hack/git-chglog/config.yaml --tag-filter-pattern "^${1}" "${1}.0-alpha.1.." + git-chglog --output CHANGELOG.md -c ./hack/git-chglog/config.yaml --tag-filter-pattern "^${1}" "${1}.0-alpha.0.." elif [ "$#" -eq 0 ]; then git-chglog --output CHANGELOG.md -c ./hack/git-chglog/config.yaml else diff --git a/pkg/resource/metadata.go b/pkg/resource/metadata.go new file mode 100644 index 00000000..61a0c5ad --- /dev/null +++ b/pkg/resource/metadata.go @@ -0,0 +1,66 @@ +// 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 resource + +import "fmt" + +// Metadata implements resource meta. +type Metadata struct { + ns Namespace + typ Type + id ID + ver Version +} + +// NewMetadata builds new metadata. +func NewMetadata(ns Namespace, typ Type, id ID, ver Version) Metadata { + return Metadata{ + ns: ns, + typ: typ, + id: id, + ver: ver, + } +} + +// ID returns resource ID. +func (md Metadata) ID() ID { + return md.id +} + +// Type returns resource types. +func (md Metadata) Type() Type { + return md.id +} + +// Namespace returns resource namespace. +func (md Metadata) Namespace() Namespace { + return md.ns +} + +// Version returns resource version. +func (md Metadata) Version() Version { + return md.ver +} + +// SetVersion updates resource version. +func (md Metadata) SetVersion(newVersion Version) { + md.ver = newVersion +} + +// BumpVersion increments resource version. +func (md Metadata) BumpVersion() { + if md.ver.uint64 == nil { + v := uint64(1) + + md.ver.uint64 = &v + } else { + *md.ver.uint64++ + } +} + +// String implements fmt.Stringer. +func (md Metadata) String() string { + return fmt.Sprintf("%s(%s/%s@%s)", md.typ, md.ns, md.id, md.ver) +} diff --git a/pkg/resource/null.go b/pkg/resource/null.go deleted file mode 100644 index 50e9f451..00000000 --- a/pkg/resource/null.go +++ /dev/null @@ -1,49 +0,0 @@ -package resource - -import "fmt" - -// NullResource just captures type and ID. -// -// All other methods panic if being used. -type NullResource struct { - id ID - typ Type -} - -// NewNullResource builds a NullResource. -func NewNullResource(id ID, typ Type) *NullResource { - return &NullResource{ - id: id, - typ: typ, - } -} - -// ID implements Resource interface. -func (r *NullResource) ID() ID { - return r.id -} - -// Type implements Resource interface. -func (r *NullResource) Type() Type { - return r.typ -} - -// Version implements Resource interface. -func (r *NullResource) Version() Version { - panic("not implemented for NullResource") -} - -// Spec implements Resource interface. -func (r *NullResource) Spec() interface{} { - panic("not implemented for NullResource") -} - -// Copy implements Resource interface. -func (r *NullResource) Copy() Resource { - panic("not implemented for NullResource") -} - -// String implements Resource interface. -func (r *NullResource) String() string { - return fmt.Sprintf("NullResource(%s/%s)", r.typ, r.id) -} diff --git a/pkg/resource/pointer.go b/pkg/resource/pointer.go new file mode 100644 index 00000000..20e90f82 --- /dev/null +++ b/pkg/resource/pointer.go @@ -0,0 +1,12 @@ +// 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 resource + +// Pointer is a Reference minus resource version. +type Pointer interface { + Namespace() Namespace + Type() Type + ID() ID +} diff --git a/pkg/resource/reference.go b/pkg/resource/reference.go new file mode 100644 index 00000000..c8a104b3 --- /dev/null +++ b/pkg/resource/reference.go @@ -0,0 +1,15 @@ +// 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 resource + +import "fmt" + +// Reference to a resource. +type Reference interface { + fmt.Stringer + + Pointer + Version() Version +} diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index c4984b13..737a61d2 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -1,7 +1,14 @@ +// 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 resource provides core resource definition. package resource -import "fmt" +import ( + "fmt" + "strconv" +) type ( // ID is a resource ID. @@ -11,26 +18,39 @@ type ( // Type could be e.g. runtime/os/mount. Type = string // Version of a resource. - Version = string + Version struct { + *uint64 + } + // Namespace of a resource. + Namespace = string +) + +// Special version constants. +var ( + VersionUndefined = Version{} ) +func (v Version) String() string { + if v.uint64 == nil { + return "undefined" + } + + return strconv.FormatUint(*v.uint64, 10) +} + // Resource is an abstract resource managed by the state. // -// Resource is uniquely identified by tuple (Type, ID). +// Resource is uniquely identified by the tuple (Namespace, Type, ID). // Resource might have additional opaque data in Spec(). // When resource is updated, Version should change with each update. type Resource interface { // String() method for debugging/logging. fmt.Stringer - // Resource identifier (Type, ID). - Type() Type - ID() ID - - // Version is used to track and resolve conflicts. + // Metadata for the resource. // - // Version should change each time Spec changes. - Version() Version + // Metadata.Version should change each time Spec changes. + Metadata() Metadata // Opaque data resource contains. Spec() interface{} diff --git a/pkg/resource/resource_test.go b/pkg/resource/resource_test.go index 0610a59b..ac1f26fe 100644 --- a/pkg/resource/resource_test.go +++ b/pkg/resource/resource_test.go @@ -1,3 +1,7 @@ +// 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 resource_test import ( @@ -9,5 +13,11 @@ import ( ) func TestInterfaces(t *testing.T) { - assert.Implements(t, (*resource.Resource)(nil), new(resource.NullResource)) + assert.Implements(t, (*resource.Reference)(nil), resource.Metadata{}) + assert.Implements(t, (*resource.Resource)(nil), new(resource.Tombstone)) +} + +func TestIsTombstone(t *testing.T) { + assert.True(t, resource.IsTombstone(new(resource.Tombstone))) + assert.False(t, resource.IsTombstone((resource.Resource)(nil))) } diff --git a/pkg/resource/tombstone.go b/pkg/resource/tombstone.go new file mode 100644 index 00000000..7324cf32 --- /dev/null +++ b/pkg/resource/tombstone.go @@ -0,0 +1,59 @@ +// 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 resource + +import "fmt" + +// Tombstone is a resource without a Spec. +// +// Tombstones are used to present state of a deleted resource. +type Tombstone struct { + ref Metadata +} + +// NewTombstone builds a tombstone from resource reference. +func NewTombstone(ref Reference) *Tombstone { + return &Tombstone{ + ref: NewMetadata(ref.Namespace(), ref.Type(), ref.ID(), ref.Version()), + } +} + +// String method for debugging/logging. +func (t *Tombstone) String() string { + return fmt.Sprintf("Tombstone(%s)", t.ref.String()) +} + +// Metadata for the resource. +// +// Metadata.Version should change each time Spec changes. +func (t *Tombstone) Metadata() Metadata { + return t.ref +} + +// Spec is not implemented for tobmstones. +func (t *Tombstone) Spec() interface{} { + panic("tombstone doesn't contain spec") +} + +// Copy returns self, as tombstone is immutable. +func (t *Tombstone) Copy() Resource { + return t +} + +// Tombstone implements Tombstoned interface. +func (t *Tombstone) Tombstone() { +} + +// Tombstoned is a marker interface for Tombstones. +type Tombstoned interface { + Tombstone() +} + +// IsTombstone checks if resource is represented by the Tombstone. +func IsTombstone(res Resource) bool { + _, ok := res.(Tombstoned) + + return ok +} diff --git a/pkg/state/condition.go b/pkg/state/condition.go index f942b7e5..34c6c5d6 100644 --- a/pkg/state/condition.go +++ b/pkg/state/condition.go @@ -1,3 +1,7 @@ +// 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 state import "github.com/talos-systems/os-runtime/pkg/resource" diff --git a/pkg/state/errors.go b/pkg/state/errors.go index 81c6445f..b3ae876e 100644 --- a/pkg/state/errors.go +++ b/pkg/state/errors.go @@ -1,3 +1,7 @@ +// 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 state import "errors" diff --git a/pkg/state/impl/local/collection.go b/pkg/state/impl/local/collection.go index 903ab178..ee95f376 100644 --- a/pkg/state/impl/local/collection.go +++ b/pkg/state/impl/local/collection.go @@ -1,3 +1,7 @@ +// 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 local import ( @@ -23,17 +27,19 @@ type ResourceCollection struct { cap int gap int + ns resource.Namespace typ resource.Type } // NewResourceCollection returns new ResourceCollection. -func NewResourceCollection(typ resource.Type) *ResourceCollection { +func NewResourceCollection(ns resource.Namespace, typ resource.Type) *ResourceCollection { const ( cap = 1000 gap = 10 ) collection := &ResourceCollection{ + ns: ns, typ: typ, cap: cap, gap: gap, @@ -62,7 +68,7 @@ func (collection *ResourceCollection) Get(resourceID resource.ID) (resource.Reso res, exists := collection.storage[resourceID] if !exists { - return nil, ErrNotFound(resource.NewNullResource(collection.typ, resourceID)) + return nil, ErrNotFound(resource.NewMetadata(collection.ns, collection.typ, resourceID, resource.VersionUndefined)) } return res.Copy(), nil @@ -71,13 +77,13 @@ func (collection *ResourceCollection) Get(resourceID resource.ID) (resource.Reso // Create a resource. func (collection *ResourceCollection) Create(resource resource.Resource) error { resource = resource.Copy() - id := resource.ID() + id := resource.Metadata().ID() collection.mu.Lock() defer collection.mu.Unlock() if _, exists := collection.storage[id]; exists { - return ErrAlreadyExists(resource) + return ErrAlreadyExists(resource.Metadata()) } collection.storage[id] = resource @@ -92,18 +98,18 @@ func (collection *ResourceCollection) Create(resource resource.Resource) error { // Update a resource. func (collection *ResourceCollection) Update(curVersion resource.Version, newResource resource.Resource) error { newResource = newResource.Copy() - id := newResource.ID() + id := newResource.Metadata().ID() collection.mu.Lock() defer collection.mu.Unlock() curResource, exists := collection.storage[id] if !exists { - return ErrNotFound(newResource) + return ErrNotFound(newResource.Metadata()) } - if curResource.Version() != curVersion { - return ErrVersionConflict(curResource, curVersion, curResource.Version()) + if curResource.Metadata().Version() != curVersion { + return ErrVersionConflict(curResource.Metadata(), curVersion, curResource.Metadata().Version()) } collection.storage[id] = newResource @@ -117,20 +123,24 @@ func (collection *ResourceCollection) Update(curVersion resource.Version, newRes } // Teardown a resource. -func (collection *ResourceCollection) Teardown(resource resource.Resource) error { - id := resource.ID() +func (collection *ResourceCollection) Teardown(ref resource.Reference) error { + id := ref.ID() collection.mu.Lock() defer collection.mu.Unlock() - _, exists := collection.storage[id] + resource, exists := collection.storage[id] if !exists { - return ErrNotFound(resource) + return ErrNotFound(ref) + } + + if resource.Metadata().Version() != ref.Version() { + return ErrVersionConflict(ref, ref.Version(), resource.Metadata().Version()) } _, torndown := collection.rip[id] if torndown { - return ErrAlreadyTorndown(resource) + return ErrAlreadyTorndown(resource.Metadata()) } collection.rip[id] = struct{}{} @@ -144,15 +154,15 @@ func (collection *ResourceCollection) Teardown(resource resource.Resource) error } // Destroy a resource. -func (collection *ResourceCollection) Destroy(resource resource.Resource) error { - id := resource.ID() +func (collection *ResourceCollection) Destroy(ref resource.Reference) error { + id := ref.ID() collection.mu.Lock() defer collection.mu.Unlock() - _, exists := collection.storage[id] + resource, exists := collection.storage[id] if !exists { - return ErrNotFound(resource) + return ErrNotFound(ref) } delete(collection.storage, id) @@ -160,7 +170,7 @@ func (collection *ResourceCollection) Destroy(resource resource.Resource) error collection.publish(state.Event{ Type: state.Destroyed, - Resource: resource.Copy(), + Resource: resource, }) return nil @@ -189,7 +199,7 @@ func (collection *ResourceCollection) Watch(ctx context.Context, id resource.ID, event.Type = state.Created } } else { - event.Resource = resource.NewNullResource(collection.typ, id) + event.Resource = resource.NewTombstone(resource.NewMetadata(collection.ns, collection.typ, id, resource.VersionUndefined)) event.Type = state.Destroyed } @@ -229,14 +239,14 @@ func (collection *ResourceCollection) Watch(ctx context.Context, id resource.ID, event = collection.stream[pos%int64(collection.cap)] pos++ - if event.Resource.ID() == id { + if event.Resource.Metadata().ID() == id { break } } collection.mu.Unlock() - if event.Resource.ID() != id { + if event.Resource.Metadata().ID() != id { continue } diff --git a/pkg/state/impl/local/errors.go b/pkg/state/impl/local/errors.go index e5234d08..a4e6c7ed 100644 --- a/pkg/state/impl/local/errors.go +++ b/pkg/state/impl/local/errors.go @@ -1,3 +1,7 @@ +// 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 local import ( @@ -13,7 +17,7 @@ type eNotFound struct { func (eNotFound) NotFoundError() {} // ErrNotFound generates error compatible with state.ErrNotFound. -func ErrNotFound(r resource.Resource) error { +func ErrNotFound(r resource.Reference) error { return eNotFound{ fmt.Errorf("resource %s doesn't exist", r), } @@ -26,21 +30,21 @@ type eConflict struct { func (eConflict) ConflictError() {} // ErrAlreadyExists generates error compatible with state.ErrConflict. -func ErrAlreadyExists(r resource.Resource) error { +func ErrAlreadyExists(r resource.Reference) error { return eConflict{ fmt.Errorf("resource %s already exists", r), } } // ErrVersionConflict generates error compatible with state.ErrConflict. -func ErrVersionConflict(r resource.Resource, expected, found resource.Version) error { +func ErrVersionConflict(r resource.Reference, expected, found resource.Version) error { return eConflict{ fmt.Errorf("resource %s update conflict: expected version %q, actual version %q", r, expected, found), } } // ErrAlreadyTorndown generates error compatible with state.ErrConflict. -func ErrAlreadyTorndown(r resource.Resource) error { +func ErrAlreadyTorndown(r resource.Reference) error { return eConflict{ fmt.Errorf("resource %s has already been torn down", r), } diff --git a/pkg/state/impl/local/errors_test.go b/pkg/state/impl/local/errors_test.go index 4e123f28..113039bc 100644 --- a/pkg/state/impl/local/errors_test.go +++ b/pkg/state/impl/local/errors_test.go @@ -1,3 +1,6 @@ +// 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 local_test import ( @@ -11,8 +14,8 @@ import ( ) func TestErrors(t *testing.T) { - assert.True(t, state.IsNotFoundError(local.ErrNotFound(resource.NewNullResource("a", "b")))) - assert.True(t, state.IsConflictError(local.ErrAlreadyExists(resource.NewNullResource("a", "b")))) - assert.True(t, state.IsConflictError(local.ErrVersionConflict(resource.NewNullResource("a", "b"), "1", "2"))) - assert.True(t, state.IsConflictError(local.ErrAlreadyTorndown(resource.NewNullResource("a", "b")))) + assert.True(t, state.IsNotFoundError(local.ErrNotFound(resource.NewMetadata("ns", "a", "b", resource.VersionUndefined)))) + assert.True(t, state.IsConflictError(local.ErrAlreadyExists(resource.NewMetadata("ns", "a", "b", resource.VersionUndefined)))) + assert.True(t, state.IsConflictError(local.ErrVersionConflict(resource.NewMetadata("ns", "a", "b", resource.VersionUndefined), resource.VersionUndefined, resource.VersionUndefined))) + assert.True(t, state.IsConflictError(local.ErrAlreadyTorndown(resource.NewMetadata("ns", "a", "b", resource.VersionUndefined)))) } diff --git a/pkg/state/impl/local/local.go b/pkg/state/impl/local/local.go index a0d00f0e..59b372e4 100644 --- a/pkg/state/impl/local/local.go +++ b/pkg/state/impl/local/local.go @@ -1,3 +1,7 @@ +// 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 local provides an implementation of state.State in memory. package local @@ -12,11 +16,14 @@ import ( // State implements state.CoreState. type State struct { collections sync.Map + ns resource.Namespace } // NewState creates new State. -func NewState() *State { - return &State{} +func NewState(ns resource.Namespace) *State { + return &State{ + ns: ns, + } } func (state *State) getCollection(typ resource.Type) *ResourceCollection { @@ -24,7 +31,7 @@ func (state *State) getCollection(typ resource.Type) *ResourceCollection { return r.(*ResourceCollection) } - collection := NewResourceCollection(typ) + collection := NewResourceCollection(state.ns, typ) r, _ := state.collections.LoadOrStore(typ, collection) @@ -32,31 +39,31 @@ func (state *State) getCollection(typ resource.Type) *ResourceCollection { } // Get a resource. -func (state *State) Get(resourceType resource.Type, resourceID resource.ID) (resource.Resource, error) { - return state.getCollection(resourceType).Get(resourceID) +func (state *State) Get(ctx context.Context, resourcePointer resource.Pointer, opts ...state.GetOption) (resource.Resource, error) { + return state.getCollection(resourcePointer.Type()).Get(resourcePointer.ID()) } // Create a resource. -func (state *State) Create(resource resource.Resource) error { - return state.getCollection(resource.Type()).Create(resource) +func (state *State) Create(ctx context.Context, resource resource.Resource, opts ...state.CreateOption) error { + return state.getCollection(resource.Metadata().Type()).Create(resource) } // Update a resource. -func (state *State) Update(curVersion resource.Version, newResource resource.Resource) error { - return state.getCollection(newResource.Type()).Update(curVersion, newResource) +func (state *State) Update(ctx context.Context, curVersion resource.Version, newResource resource.Resource, opts ...state.UpdateOption) error { + return state.getCollection(newResource.Metadata().Type()).Update(curVersion, newResource) } // Teardown a resource. -func (state *State) Teardown(resource resource.Resource) error { - return state.getCollection(resource.Type()).Teardown(resource) +func (state *State) Teardown(ctx context.Context, resourceReference resource.Reference, opts ...state.TeardownOption) error { + return state.getCollection(resourceReference.Type()).Teardown(resourceReference) } // Destroy a resource. -func (state *State) Destroy(resource resource.Resource) error { - return state.getCollection(resource.Type()).Destroy(resource) +func (state *State) Destroy(ctx context.Context, resourceReference resource.Reference, opts ...state.DestroyOption) error { + return state.getCollection(resourceReference.Type()).Destroy(resourceReference) } // Watch a resource. -func (state *State) Watch(ctx context.Context, typ resource.Type, id resource.ID, ch chan<- state.Event) error { - return state.getCollection(typ).Watch(ctx, id, ch) +func (state *State) Watch(ctx context.Context, resourcePointer resource.Pointer, ch chan<- state.Event, opts ...state.WatchOption) error { + return state.getCollection(resourcePointer.Type()).Watch(ctx, resourcePointer.ID(), ch) } diff --git a/pkg/state/impl/local/local_test.go b/pkg/state/impl/local/local_test.go index 73d3b6bb..42819f58 100644 --- a/pkg/state/impl/local/local_test.go +++ b/pkg/state/impl/local/local_test.go @@ -1,3 +1,7 @@ +// 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 local_test import ( diff --git a/pkg/state/options.go b/pkg/state/options.go new file mode 100644 index 00000000..a99540ba --- /dev/null +++ b/pkg/state/options.go @@ -0,0 +1,41 @@ +// 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 state + +// GetOptions for the CoreState.Get function. +type GetOptions struct{} + +// GetOption builds GetOptions. +type GetOption func(*GetOptions) + +// CreateOptions for the CoreState.Create function. +type CreateOptions struct{} + +// CreateOption builds CreateOptions. +type CreateOption func(*CreateOptions) + +// UpdateOptions for the CoreState.Update function. +type UpdateOptions struct{} + +// UpdateOption builds UpdateOptions. +type UpdateOption func(*UpdateOptions) + +// TeardownOptions for the CoreState.Teardown function. +type TeardownOptions struct{} + +// TeardownOption builds TeardownOptions. +type TeardownOption func(*TeardownOptions) + +// DestroyOptions for the CoreState.Destroy function. +type DestroyOptions struct{} + +// DestroyOption builds DestroyOptions. +type DestroyOption func(*DestroyOptions) + +// WatchOptions for the CoreState.Watch function. +type WatchOptions struct{} + +// WatchOption builds WatchOptions. +type WatchOption func(*WatchOptions) diff --git a/pkg/state/state.go b/pkg/state/state.go index 66a9ae9e..5c15b2af 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -1,3 +1,7 @@ +// 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 state describes interface of the core state manager/broker. package state @@ -28,6 +32,12 @@ type Event struct { Resource resource.Resource } +// NamespacedState allows to create different namespaces which might be backed by different +// State implementations. +type NamespacedState interface { + Namespace(resource.Namespace) State +} + // CoreState is the central broker in the system handling state and changes. // // CoreState provides the core API that should be implemented. @@ -36,30 +46,31 @@ type CoreState interface { // Get a resource by type and ID. // // If a resource is not found, error is returned. - Get(resource.Type, resource.ID) (resource.Resource, error) + Get(context.Context, resource.Pointer, ...GetOption) (resource.Resource, error) // Create a resource. // // If a resource already exists, Create returns an error. - Create(resource.Resource) error + Create(context.Context, resource.Resource, ...CreateOption) error // Update a resource. // // If a resource doesn't exist, error is returned. // On update current version of resource `new` in the state should match // curVersion, otherwise conflict error is returned. - Update(curVersion resource.Version, new resource.Resource) error + Update(ctx context.Context, curVersion resource.Version, new resource.Resource, opts ...UpdateOption) error // Teardown a resource (mark as being destroyed). // // If a resource doesn't exist, error is returned. + // If resource version doesn't match, conflict error is returned. // It's an error to tear down a resource which is already being torn down. - Teardown(resource.Resource) error + Teardown(context.Context, resource.Reference, ...TeardownOption) error // Destroy a resource. // // If a resource doesn't exist, error is returned. - Destroy(resource.Resource) error + Destroy(context.Context, resource.Reference, ...DestroyOption) error // Watch state of a resource by type. // @@ -67,7 +78,7 @@ type CoreState interface { // Watch is canceled when context gets canceled. // Watch sends initial resource state as the very first event on the channel, // and then sends any updates to the resource as events. - Watch(context.Context, resource.Type, resource.ID, chan<- Event) error + Watch(context.Context, resource.Pointer, chan<- Event, ...WatchOption) error } // UpdaterFunc is called on resource to update it to the desired state. @@ -80,7 +91,7 @@ type State interface { CoreState // UpdateWithConflicts automatically handles conflicts on update. - UpdateWithConflicts(resource.Resource, UpdaterFunc) (resource.Resource, error) + UpdateWithConflicts(context.Context, resource.Resource, UpdaterFunc) (resource.Resource, error) // WatchFor watches for resource to reach all of the specified conditions. - WatchFor(context.Context, resource.Type, resource.ID, ...WatchForConditionFunc) (resource.Resource, error) + WatchFor(context.Context, resource.Pointer, ...WatchForConditionFunc) (resource.Resource, error) } diff --git a/pkg/state/wrap.go b/pkg/state/wrap.go index 6a3e69d7..c001e618 100644 --- a/pkg/state/wrap.go +++ b/pkg/state/wrap.go @@ -1,3 +1,7 @@ +// 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 state import ( @@ -18,20 +22,20 @@ type coreWrapper struct { } // UpdateWithConflicts automatically handles conflicts on update. -func (state coreWrapper) UpdateWithConflicts(r resource.Resource, f UpdaterFunc) (resource.Resource, error) { +func (state coreWrapper) UpdateWithConflicts(ctx context.Context, r resource.Resource, f UpdaterFunc) (resource.Resource, error) { for { - current, err := state.Get(r.Type(), r.ID()) + current, err := state.Get(ctx, resource.Pointer(r.Metadata())) if err != nil { return nil, err } - curVersion := current.Version() + curVersion := current.Metadata().Version() if err = f(current); err != nil { return nil, err } - err = state.Update(curVersion, current) + err = state.Update(ctx, curVersion, current) if err == nil { return current, nil } @@ -45,7 +49,7 @@ func (state coreWrapper) UpdateWithConflicts(r resource.Resource, f UpdaterFunc) } // WatchFor watches for resource to reach all of the specified conditions. -func (state coreWrapper) WatchFor(ctx context.Context, typ resource.Type, id resource.ID, conditionFunc ...WatchForConditionFunc) (resource.Resource, error) { +func (state coreWrapper) WatchFor(ctx context.Context, pointer resource.Pointer, conditionFunc ...WatchForConditionFunc) (resource.Resource, error) { var condition WatchForCondition for _, f := range conditionFunc { @@ -59,7 +63,7 @@ func (state coreWrapper) WatchFor(ctx context.Context, typ resource.Type, id res ctx, cancel := context.WithCancel(ctx) defer cancel() - if err := state.Watch(ctx, typ, id, ch); err != nil { + if err := state.Watch(ctx, pointer, ch); err != nil { return nil, err }