From cf19193254dc669f6b2141f09e084ce7bb1fc501 Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Fri, 17 Apr 2020 10:29:54 +0200 Subject: [PATCH 01/14] provisiond: rework of to create an egine - Engine is not an interface anymore - Use functional configuration to creation the engine - Allow to configure the function map used to provision/decomission workloads --- cmds/provisiond/main.go | 7 +++- pkg/provision/crypt.go | 23 ---------- pkg/provision/crypto.go | 18 ++++++++ pkg/provision/engine.go | 81 +++++++++++++++++++++++++++++------- pkg/provision/provision.go | 18 ++------ pkg/provision/reservation.go | 2 + 6 files changed, 97 insertions(+), 52 deletions(-) delete mode 100644 pkg/provision/crypt.go diff --git a/cmds/provisiond/main.go b/cmds/provisiond/main.go index 0f6057d00..a6b0972ba 100644 --- a/cmds/provisiond/main.go +++ b/cmds/provisiond/main.go @@ -114,7 +114,12 @@ func main() { provision.NewDecommissionSource(localStore), ) - engine := provision.New(nodeID.Identity(), source, localStore, cl) + engine := provision.New( + provision.WithNodeID(nodeID.Identity()), + provision.WithSource(source), + provision.WithCache(localStore), + provision.WithExplorer(cl), + ) server.Register(zbus.ObjectID{Name: module, Version: "0.0.1"}, pkg.ProvisionMonitor(engine)) diff --git a/pkg/provision/crypt.go b/pkg/provision/crypt.go deleted file mode 100644 index bd4c78d52..000000000 --- a/pkg/provision/crypt.go +++ /dev/null @@ -1,23 +0,0 @@ -package provision - -import ( - "encoding/hex" - - "github.com/threefoldtech/zbus" - "github.com/threefoldtech/zos/pkg/stubs" -) - -func decryptSecret(client zbus.Client, secret string) (string, error) { - if len(secret) == 0 { - return "", nil - } - identity := stubs.NewIdentityManagerStub(client) - - bytes, err := hex.DecodeString(secret) - if err != nil { - return "", err - } - - out, err := identity.Decrypt(bytes) - return string(out), err -} diff --git a/pkg/provision/crypto.go b/pkg/provision/crypto.go index 78a56b298..c1fa587fd 100644 --- a/pkg/provision/crypto.go +++ b/pkg/provision/crypto.go @@ -2,11 +2,14 @@ package provision import ( "bytes" + "encoding/hex" "github.com/pkg/errors" + "github.com/threefoldtech/zbus" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/crypto" + "github.com/threefoldtech/zos/pkg/stubs" "golang.org/x/crypto/ed25519" ) @@ -73,3 +76,18 @@ func Verify(r *Reservation) error { return crypto.Verify(publicKey, buf.Bytes(), r.Signature) } + +func decryptSecret(client zbus.Client, secret string) (string, error) { + if len(secret) == 0 { + return "", nil + } + identity := stubs.NewIdentityManagerStub(client) + + bytes, err := hex.DecodeString(secret) + if err != nil { + return "", err + } + + out, err := identity.Decrypt(bytes) + return string(out), err +} diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go index 5d947476d..30527dd6b 100644 --- a/pkg/provision/engine.go +++ b/pkg/provision/engine.go @@ -34,31 +34,84 @@ type Feedbacker interface { UpdateReservedResources(nodeID string, c Counters) error } -type defaultEngine struct { +type Engine struct { + nodeID string + source ReservationSource + store ReservationCache + cl *client.Client + provisioners map[ReservationType]Provisioner + decomissioners map[ReservationType]Decommissioner +} + +type option func(*Engine) + +type engineOptions struct { nodeID string source ReservationSource store ReservationCache cl *client.Client } +func WithNodeID(nodeID string) option { + return func(e *Engine) { + e.nodeID = nodeID + } +} +func WithSource(s ReservationSource) option { + return func(e *Engine) { + e.source = s + } +} + +func WithCache(c ReservationCache) option { + return func(e *Engine) { + e.store = c + } +} + +func WithExplorer(c *client.Client) option { + return func(e *Engine) { + e.cl = c + } +} + +func WithProvisioners(m map[ReservationType]Provisioner) option { + return func(e *Engine) { + e.provisioners = m + } +} + +func WithDecomissioners(m map[ReservationType]Decommissioner) option { + return func(e *Engine) { + e.decomissioners = m + } +} + // New creates a new engine. Once started, the engine // will continue processing all reservations from the reservation source // and try to apply them. // the default implementation is a single threaded worker. so it process // one reservation at a time. On error, the engine will log the error. and // continue to next reservation. -func New(nodeID string, source ReservationSource, rw ReservationCache, cl *client.Client) Engine { - return &defaultEngine{ - nodeID: nodeID, - source: source, - store: rw, - cl: cl, +func New(opts ...option) *Engine { + e := &Engine{} + for _, f := range opts { + f(e) + } + if e.provisioners == nil { + e.provisioners = provisioners } + + if e.decomissioners == nil { + e.decomissioners = decommissioners + } + + return e } // Run starts processing reservation resource. Then try to allocate // reservations -func (e *defaultEngine) Run(ctx context.Context) error { +func (e *Engine) Run(ctx context.Context) error { ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -108,7 +161,7 @@ func (e *defaultEngine) Run(ctx context.Context) error { } } -func (e defaultEngine) capacityUsed() (directory.ResourceAmount, directory.WorkloadAmount) { +func (e Engine) capacityUsed() (directory.ResourceAmount, directory.WorkloadAmount) { counters := e.store.Counters() resources := directory.ResourceAmount{ @@ -128,7 +181,7 @@ func (e defaultEngine) capacityUsed() (directory.ResourceAmount, directory.Workl return resources, workloads } -func (e *defaultEngine) updateReservedCapacity() error { +func (e *Engine) updateReservedCapacity() error { resources, workloads := e.capacityUsed() log.Info().Msgf("reserved resource %+v", resources) log.Info().Msgf("provisionned workloads %+v", workloads) @@ -136,7 +189,7 @@ func (e *defaultEngine) updateReservedCapacity() error { return e.cl.Directory.NodeUpdateUsedResources(e.nodeID, resources, workloads) } -func (e *defaultEngine) provision(ctx context.Context, r *Reservation) error { +func (e *Engine) provision(ctx context.Context, r *Reservation) error { if err := r.validate(); err != nil { return errors.Wrapf(err, "failed validation of reservation") } @@ -169,7 +222,7 @@ func (e *defaultEngine) provision(ctx context.Context, r *Reservation) error { return nil } -func (e *defaultEngine) decommission(ctx context.Context, r *Reservation) error { +func (e *Engine) decommission(ctx context.Context, r *Reservation) error { fn, ok := decommissioners[r.Type] if !ok { return fmt.Errorf("type of reservation not supported: %s", r.Type) @@ -204,7 +257,7 @@ func (e *defaultEngine) decommission(ctx context.Context, r *Reservation) error return nil } -func (e *defaultEngine) reply(ctx context.Context, r *Reservation, rErr error, info interface{}) error { +func (e *Engine) reply(ctx context.Context, r *Reservation, rErr error, info interface{}) error { log.Debug().Str("id", r.ID).Msg("sending reply for reservation") zbus := GetZBus(ctx) @@ -249,7 +302,7 @@ func (e *defaultEngine) reply(ctx context.Context, r *Reservation, rErr error, i return e.cl.Workloads.WorkloadPutResult(e.nodeID, r.ID, result.ToSchemaType()) } -func (e *defaultEngine) Counters(ctx context.Context) <-chan pkg.ProvisionCounters { +func (e *Engine) Counters(ctx context.Context) <-chan pkg.ProvisionCounters { ch := make(chan pkg.ProvisionCounters) go func() { for { diff --git a/pkg/provision/provision.go b/pkg/provision/provision.go index 566ad146d..e5f0ec532 100644 --- a/pkg/provision/provision.go +++ b/pkg/provision/provision.go @@ -9,8 +9,6 @@ package provision import ( "context" - - "github.com/threefoldtech/zos/pkg" ) // ReservationSource interface. The source @@ -21,22 +19,14 @@ type ReservationSource interface { Reservations(ctx context.Context) <-chan *Reservation } -// Engine interface -type Engine interface { - // Start the engine - Run(ctx context.Context) error - // Counters stream for number of provisioned resources - Counters(ctx context.Context) <-chan pkg.ProvisionCounters -} - -type provisioner func(ctx context.Context, reservation *Reservation) (interface{}, error) -type decommissioner func(ctx context.Context, reservation *Reservation) error +type Provisioner func(ctx context.Context, reservation *Reservation) (interface{}, error) +type Decommissioner func(ctx context.Context, reservation *Reservation) error var ( // provisioners defines the entry point for the different // reservation provisioners. Currently only containers are // supported. - provisioners = map[ReservationType]provisioner{ + provisioners = map[ReservationType]Provisioner{ ContainerReservation: containerProvision, VolumeReservation: volumeProvision, NetworkReservation: networkProvision, @@ -45,7 +35,7 @@ var ( KubernetesReservation: kubernetesProvision, } - decommissioners = map[ReservationType]decommissioner{ + decommissioners = map[ReservationType]Decommissioner{ ContainerReservation: containerDecommission, VolumeReservation: volumeDecommission, NetworkReservation: networkDecommission, diff --git a/pkg/provision/reservation.go b/pkg/provision/reservation.go index 50010598a..73383eb8e 100644 --- a/pkg/provision/reservation.go +++ b/pkg/provision/reservation.go @@ -33,6 +33,8 @@ const ( DebugReservation ReservationType = "debug" // KubernetesReservation type KubernetesReservation ReservationType = "kubernetes" + // TCPProxyReservation type + TCPProxyReservation ReservationType = "tc-proxy" ) var ( From b233e76f2771e65b37a49908e038f8e4ffc0553d Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Fri, 17 Apr 2020 13:48:03 +0200 Subject: [PATCH 02/14] extract explorer --- cmds/capacityd/main.go | 50 +- cmds/identityd/main.go | 4 +- cmds/networkd/addr_watcher.go | 6 +- cmds/networkd/main.go | 2 +- cmds/networkd/pubiface.go | 2 +- go.mod | 47 +- go.sum | 28 + pkg/app/boot.go | 8 +- pkg/app/explorer.go | 2 +- pkg/network/networker.go | 2 +- pkg/network/types/types.go | 4 +- pkg/network/types/types_test.go | 4 +- pkg/provision/converter.go | 2 +- pkg/provision/converter_test.go | 4 +- pkg/provision/engine.go | 4 +- pkg/provision/owner_cache.go | 2 +- pkg/provision/reservation.go | 6 +- pkg/provision/source.go | 2 +- pkg/schema/README.md | 10 - pkg/schema/generate.go | 216 ----- pkg/schema/generate_test.go | 333 -------- pkg/schema/parse.go | 383 --------- pkg/schema/parse_test.go | 374 --------- pkg/schema/types.go | 256 ------ pkg/schema/types_test.go | 265 ------ tools/client/filter.go | 96 --- tools/client/http.go | 216 ----- tools/client/phonebook.go | 64 -- tools/client/signer.go | 91 -- tools/client/workloads.go | 153 ---- tools/explorer/.gitignore | 5 - tools/explorer/config/global.go | 37 - tools/explorer/frontend/.gitignore | 25 - tools/explorer/frontend/babel.config.js | 5 - tools/explorer/frontend/package.json | 65 -- tools/explorer/frontend/public/config.js | 5 - tools/explorer/frontend/public/favicon.ico | Bin 15086 -> 0 bytes tools/explorer/frontend/public/index.html | 21 - tools/explorer/frontend/readme.md | 46 - tools/explorer/frontend/src/App.vue | 144 ---- tools/explorer/frontend/src/assets/logo.jpg | Bin 4834 -> 0 bytes tools/explorer/frontend/src/assets/logo.png | Bin 6849 -> 0 bytes tools/explorer/frontend/src/assets/logo.svg | 1 - .../components/capacitymap/capacitymap.css | 3 - .../components/capacitymap/capacitymap.html | 36 - .../src/components/capacitymap/capacitymap.js | 61 -- .../components/capacitymap/capacitymap.scss | 3 - .../capacitymap/capacitymap.spec.js | 29 - .../src/components/capacitymap/index.vue | 3 - .../capacityselector/capacityselector.css | 3 - .../capacityselector/capacityselector.html | 5 - .../capacityselector/capacityselector.js | 54 -- .../capacityselector/capacityselector.scss | 3 - .../capacityselector/capacityselector.spec.js | 29 - .../src/components/capacityselector/index.vue | 3 - .../src/components/minigraph/index.vue | 3 - .../src/components/minigraph/minigraph.css | 3 - .../src/components/minigraph/minigraph.html | 18 - .../src/components/minigraph/minigraph.js | 55 -- .../src/components/minigraph/minigraph.scss | 3 - .../components/minigraph/minigraph.spec.js | 29 - .../src/components/nodeinfo/index.vue | 3 - .../src/components/nodeinfo/nodeinfo.css | 7 - .../src/components/nodeinfo/nodeinfo.html | 132 --- .../src/components/nodeinfo/nodeinfo.js | 17 - .../src/components/nodeinfo/nodeinfo.scss | 3 - .../src/components/nodeinfo/nodeinfo.spec.js | 29 - .../src/components/nodestable/index.vue | 3 - .../src/components/nodestable/nodestable.css | 4 - .../src/components/nodestable/nodestable.html | 79 -- .../src/components/nodestable/nodestable.js | 127 --- .../src/components/nodestable/nodestable.scss | 3 - .../components/nodestable/nodestable.spec.js | 29 - .../src/components/scrollablecard/index.vue | 3 - .../scrollablecard/scrollablecard.css | 3 - .../scrollablecard/scrollablecard.html | 31 - .../scrollablecard/scrollablecard.js | 23 - .../scrollablecard/scrollablecard.scss | 3 - .../scrollablecard/scrollablecard.spec.js | 29 - tools/explorer/frontend/src/main.js | 15 - .../frontend/src/plugins/chartkick.js | 5 - tools/explorer/frontend/src/plugins/index.js | 2 - tools/explorer/frontend/src/plugins/maps.js | 7 - .../explorer/frontend/src/plugins/vuetify.js | 17 - tools/explorer/frontend/src/router.js | 22 - .../frontend/src/services/tfService.js | 65 -- tools/explorer/frontend/src/store/index.js | 11 - .../frontend/src/store/transaction.js | 146 ---- .../frontend/src/views/capacity/capacity.css | 19 - .../frontend/src/views/capacity/capacity.html | 67 -- .../frontend/src/views/capacity/capacity.js | 63 -- .../frontend/src/views/capacity/capacity.scss | 3 - .../src/views/capacity/capacity.spec.js | 29 - .../frontend/src/views/capacity/index.vue | 3 - tools/explorer/frontend/vue.config.js | 5 - tools/explorer/main.go | 234 ------ .../container_missing_fields/main.go | 110 --- .../explorer/migrations/migration/convert.py | 28 - .../migrations/migration/migration.go | 192 ----- tools/explorer/migrations/migration/readme.me | 15 - .../migrations/user_ipaddresss/main.go | 97 --- .../migrations/wallet_address/main.go | 107 --- tools/explorer/models/generate.go | 9 - .../models/generated/directory/directory.go | 231 ----- .../models/generated/phonebook/phonebook.go | 26 - .../models/generated/workloads/workloads.go | 481 ----------- tools/explorer/models/id.go | 51 -- tools/explorer/models/readme.md | 4 - .../directory/tfgrid_directory_farm_1.toml | 24 - .../tfgrid_directory_location_1.toml | 7 - .../directory/tfgrid_directory_node_2.toml | 64 -- .../phonebook/tfgrid_phonebook_user_1.toml | 8 - .../tfgrid_workloads_reservation_1.toml | 50 -- ...rid_workloads_reservation_container_1.toml | 51 -- .../tfgrid_workloads_reservation_k8s.toml | 22 - ...fgrid_workloads_reservation_network_1.toml | 28 - ...tfgrid_workloads_reservation_result_1.toml | 11 - ...rkloads_reservation_statsaggregator_1.toml | 6 - ...tfgrid_workloads_reservation_volume_1.toml | 12 - ...grid_workloads_reservation_workload_1.toml | 9 - .../tfgrid_workloads_reservation_zdb_1.toml | 15 - tools/explorer/models/utils.go | 77 -- tools/explorer/mw/action.go | 143 ---- tools/explorer/mw/auth.go | 124 --- tools/explorer/mw/db.go | 48 -- .../explorer/pkg/directory/farms_handlers.go | 122 --- tools/explorer/pkg/directory/farms_store.go | 69 -- tools/explorer/pkg/directory/node_handlers.go | 318 ------- tools/explorer/pkg/directory/nodes_store.go | 260 ------ tools/explorer/pkg/directory/setup.go | 52 -- tools/explorer/pkg/directory/types/farm.go | 194 ----- tools/explorer/pkg/directory/types/node.go | 346 -------- tools/explorer/pkg/directory/types/setup.go | 54 -- tools/explorer/pkg/escrow/escrow.go | 52 -- .../pkg/escrow/payout_distribution.go | 34 - .../pkg/escrow/payout_distribution_test.go | 37 - tools/explorer/pkg/escrow/resource.go | 171 ---- tools/explorer/pkg/escrow/resource_test.go | 595 ------------- tools/explorer/pkg/escrow/stellar.go | 656 --------------- tools/explorer/pkg/escrow/stellar_test.go | 91 -- tools/explorer/pkg/escrow/types/address.go | 69 -- tools/explorer/pkg/escrow/types/escrow.go | 141 ---- tools/explorer/pkg/escrow/types/setup.go | 42 - tools/explorer/pkg/phonebook/setup.go | 29 - tools/explorer/pkg/phonebook/types/setup.go | 31 - tools/explorer/pkg/phonebook/types/user.go | 240 ------ .../explorer/pkg/phonebook/types/user_test.go | 63 -- tools/explorer/pkg/phonebook/user.go | 200 ----- tools/explorer/pkg/phonebook/user_test.go | 1 - tools/explorer/pkg/stellar/asset.go | 63 -- tools/explorer/pkg/stellar/asset_test.go | 65 -- tools/explorer/pkg/stellar/crypto.go | 86 -- tools/explorer/pkg/stellar/crypto_test.go | 63 -- tools/explorer/pkg/stellar/stellar.go | 625 -------------- tools/explorer/pkg/stellar/validation.go | 81 -- tools/explorer/pkg/workloads/reservation.go | 792 ------------------ tools/explorer/pkg/workloads/setup.go | 36 - .../explorer/pkg/workloads/types/pipeline.go | 150 ---- .../pkg/workloads/types/reservation.go | 644 -------------- .../pkg/workloads/types/reservation_test.go | 49 -- tools/explorer/pkg/workloads/types/setup.go | 52 -- tools/explorer/readme.md | 77 -- tools/schemac/main.go | 2 +- tools/stellar/multisig.go | 2 +- tools/tffarmer/farm.go | 4 +- tools/tffarmer/main.go | 2 +- tools/tffarmer/network.go | 4 +- tools/tfuser/cmds_identity.go | 4 +- tools/tfuser/cmds_live.go | 4 +- tools/tfuser/cmds_provision.go | 4 +- tools/tfuser/main.go | 29 +- 171 files changed, 128 insertions(+), 13201 deletions(-) delete mode 100644 pkg/schema/README.md delete mode 100644 pkg/schema/generate.go delete mode 100644 pkg/schema/generate_test.go delete mode 100644 pkg/schema/parse.go delete mode 100644 pkg/schema/parse_test.go delete mode 100644 pkg/schema/types.go delete mode 100644 pkg/schema/types_test.go delete mode 100644 tools/client/filter.go delete mode 100644 tools/client/http.go delete mode 100644 tools/client/phonebook.go delete mode 100644 tools/client/signer.go delete mode 100644 tools/client/workloads.go delete mode 100644 tools/explorer/.gitignore delete mode 100644 tools/explorer/config/global.go delete mode 100644 tools/explorer/frontend/.gitignore delete mode 100644 tools/explorer/frontend/babel.config.js delete mode 100644 tools/explorer/frontend/package.json delete mode 100644 tools/explorer/frontend/public/config.js delete mode 100644 tools/explorer/frontend/public/favicon.ico delete mode 100644 tools/explorer/frontend/public/index.html delete mode 100644 tools/explorer/frontend/readme.md delete mode 100644 tools/explorer/frontend/src/App.vue delete mode 100644 tools/explorer/frontend/src/assets/logo.jpg delete mode 100644 tools/explorer/frontend/src/assets/logo.png delete mode 100644 tools/explorer/frontend/src/assets/logo.svg delete mode 100644 tools/explorer/frontend/src/components/capacitymap/capacitymap.css delete mode 100644 tools/explorer/frontend/src/components/capacitymap/capacitymap.html delete mode 100644 tools/explorer/frontend/src/components/capacitymap/capacitymap.js delete mode 100644 tools/explorer/frontend/src/components/capacitymap/capacitymap.scss delete mode 100644 tools/explorer/frontend/src/components/capacitymap/capacitymap.spec.js delete mode 100644 tools/explorer/frontend/src/components/capacitymap/index.vue delete mode 100644 tools/explorer/frontend/src/components/capacityselector/capacityselector.css delete mode 100644 tools/explorer/frontend/src/components/capacityselector/capacityselector.html delete mode 100644 tools/explorer/frontend/src/components/capacityselector/capacityselector.js delete mode 100644 tools/explorer/frontend/src/components/capacityselector/capacityselector.scss delete mode 100644 tools/explorer/frontend/src/components/capacityselector/capacityselector.spec.js delete mode 100644 tools/explorer/frontend/src/components/capacityselector/index.vue delete mode 100644 tools/explorer/frontend/src/components/minigraph/index.vue delete mode 100644 tools/explorer/frontend/src/components/minigraph/minigraph.css delete mode 100644 tools/explorer/frontend/src/components/minigraph/minigraph.html delete mode 100644 tools/explorer/frontend/src/components/minigraph/minigraph.js delete mode 100644 tools/explorer/frontend/src/components/minigraph/minigraph.scss delete mode 100644 tools/explorer/frontend/src/components/minigraph/minigraph.spec.js delete mode 100644 tools/explorer/frontend/src/components/nodeinfo/index.vue delete mode 100644 tools/explorer/frontend/src/components/nodeinfo/nodeinfo.css delete mode 100644 tools/explorer/frontend/src/components/nodeinfo/nodeinfo.html delete mode 100644 tools/explorer/frontend/src/components/nodeinfo/nodeinfo.js delete mode 100644 tools/explorer/frontend/src/components/nodeinfo/nodeinfo.scss delete mode 100644 tools/explorer/frontend/src/components/nodeinfo/nodeinfo.spec.js delete mode 100644 tools/explorer/frontend/src/components/nodestable/index.vue delete mode 100644 tools/explorer/frontend/src/components/nodestable/nodestable.css delete mode 100644 tools/explorer/frontend/src/components/nodestable/nodestable.html delete mode 100644 tools/explorer/frontend/src/components/nodestable/nodestable.js delete mode 100644 tools/explorer/frontend/src/components/nodestable/nodestable.scss delete mode 100644 tools/explorer/frontend/src/components/nodestable/nodestable.spec.js delete mode 100644 tools/explorer/frontend/src/components/scrollablecard/index.vue delete mode 100644 tools/explorer/frontend/src/components/scrollablecard/scrollablecard.css delete mode 100644 tools/explorer/frontend/src/components/scrollablecard/scrollablecard.html delete mode 100644 tools/explorer/frontend/src/components/scrollablecard/scrollablecard.js delete mode 100644 tools/explorer/frontend/src/components/scrollablecard/scrollablecard.scss delete mode 100644 tools/explorer/frontend/src/components/scrollablecard/scrollablecard.spec.js delete mode 100644 tools/explorer/frontend/src/main.js delete mode 100644 tools/explorer/frontend/src/plugins/chartkick.js delete mode 100644 tools/explorer/frontend/src/plugins/index.js delete mode 100644 tools/explorer/frontend/src/plugins/maps.js delete mode 100644 tools/explorer/frontend/src/plugins/vuetify.js delete mode 100644 tools/explorer/frontend/src/router.js delete mode 100644 tools/explorer/frontend/src/services/tfService.js delete mode 100644 tools/explorer/frontend/src/store/index.js delete mode 100644 tools/explorer/frontend/src/store/transaction.js delete mode 100644 tools/explorer/frontend/src/views/capacity/capacity.css delete mode 100644 tools/explorer/frontend/src/views/capacity/capacity.html delete mode 100644 tools/explorer/frontend/src/views/capacity/capacity.js delete mode 100644 tools/explorer/frontend/src/views/capacity/capacity.scss delete mode 100644 tools/explorer/frontend/src/views/capacity/capacity.spec.js delete mode 100644 tools/explorer/frontend/src/views/capacity/index.vue delete mode 100644 tools/explorer/frontend/vue.config.js delete mode 100644 tools/explorer/main.go delete mode 100644 tools/explorer/migrations/container_missing_fields/main.go delete mode 100644 tools/explorer/migrations/migration/convert.py delete mode 100644 tools/explorer/migrations/migration/migration.go delete mode 100644 tools/explorer/migrations/migration/readme.me delete mode 100644 tools/explorer/migrations/user_ipaddresss/main.go delete mode 100644 tools/explorer/migrations/wallet_address/main.go delete mode 100644 tools/explorer/models/generate.go delete mode 100644 tools/explorer/models/generated/directory/directory.go delete mode 100644 tools/explorer/models/generated/phonebook/phonebook.go delete mode 100644 tools/explorer/models/generated/workloads/workloads.go delete mode 100644 tools/explorer/models/id.go delete mode 100644 tools/explorer/models/readme.md delete mode 100644 tools/explorer/models/schema/directory/tfgrid_directory_farm_1.toml delete mode 100644 tools/explorer/models/schema/directory/tfgrid_directory_location_1.toml delete mode 100644 tools/explorer/models/schema/directory/tfgrid_directory_node_2.toml delete mode 100644 tools/explorer/models/schema/phonebook/tfgrid_phonebook_user_1.toml delete mode 100644 tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_1.toml delete mode 100644 tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_container_1.toml delete mode 100644 tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_k8s.toml delete mode 100644 tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_network_1.toml delete mode 100644 tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_result_1.toml delete mode 100644 tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_statsaggregator_1.toml delete mode 100644 tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_volume_1.toml delete mode 100644 tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_workload_1.toml delete mode 100644 tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_zdb_1.toml delete mode 100644 tools/explorer/models/utils.go delete mode 100644 tools/explorer/mw/action.go delete mode 100644 tools/explorer/mw/auth.go delete mode 100644 tools/explorer/mw/db.go delete mode 100644 tools/explorer/pkg/directory/farms_handlers.go delete mode 100644 tools/explorer/pkg/directory/farms_store.go delete mode 100644 tools/explorer/pkg/directory/node_handlers.go delete mode 100644 tools/explorer/pkg/directory/nodes_store.go delete mode 100644 tools/explorer/pkg/directory/setup.go delete mode 100644 tools/explorer/pkg/directory/types/farm.go delete mode 100644 tools/explorer/pkg/directory/types/node.go delete mode 100644 tools/explorer/pkg/directory/types/setup.go delete mode 100644 tools/explorer/pkg/escrow/escrow.go delete mode 100644 tools/explorer/pkg/escrow/payout_distribution.go delete mode 100644 tools/explorer/pkg/escrow/payout_distribution_test.go delete mode 100644 tools/explorer/pkg/escrow/resource.go delete mode 100644 tools/explorer/pkg/escrow/resource_test.go delete mode 100644 tools/explorer/pkg/escrow/stellar.go delete mode 100644 tools/explorer/pkg/escrow/stellar_test.go delete mode 100644 tools/explorer/pkg/escrow/types/address.go delete mode 100644 tools/explorer/pkg/escrow/types/escrow.go delete mode 100644 tools/explorer/pkg/escrow/types/setup.go delete mode 100644 tools/explorer/pkg/phonebook/setup.go delete mode 100644 tools/explorer/pkg/phonebook/types/setup.go delete mode 100644 tools/explorer/pkg/phonebook/types/user.go delete mode 100644 tools/explorer/pkg/phonebook/types/user_test.go delete mode 100644 tools/explorer/pkg/phonebook/user.go delete mode 100644 tools/explorer/pkg/phonebook/user_test.go delete mode 100644 tools/explorer/pkg/stellar/asset.go delete mode 100644 tools/explorer/pkg/stellar/asset_test.go delete mode 100644 tools/explorer/pkg/stellar/crypto.go delete mode 100644 tools/explorer/pkg/stellar/crypto_test.go delete mode 100644 tools/explorer/pkg/stellar/stellar.go delete mode 100644 tools/explorer/pkg/stellar/validation.go delete mode 100644 tools/explorer/pkg/workloads/reservation.go delete mode 100644 tools/explorer/pkg/workloads/setup.go delete mode 100644 tools/explorer/pkg/workloads/types/pipeline.go delete mode 100644 tools/explorer/pkg/workloads/types/reservation.go delete mode 100644 tools/explorer/pkg/workloads/types/reservation_test.go delete mode 100644 tools/explorer/pkg/workloads/types/setup.go delete mode 100644 tools/explorer/readme.md diff --git a/cmds/capacityd/main.go b/cmds/capacityd/main.go index 131b73740..1d39999e8 100644 --- a/cmds/capacityd/main.go +++ b/cmds/capacityd/main.go @@ -7,13 +7,12 @@ import ( "github.com/cenkalti/backoff/v3" + "github.com/threefoldtech/tfexplorer/client" "github.com/threefoldtech/zos/pkg/app" "github.com/threefoldtech/zos/pkg/capacity" "github.com/threefoldtech/zos/pkg/monitord" "github.com/threefoldtech/zos/pkg/stubs" "github.com/threefoldtech/zos/pkg/utils" - "github.com/threefoldtech/zos/tools/client" - "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" "github.com/rs/zerolog/log" @@ -32,7 +31,7 @@ func cap(ctx context.Context, client zbus.Client) { } // call this now so we block here until identityd is ready to serve us - nodeID := identity.NodeID().Identity() + // nodeID := identity.NodeID().Identity() r := capacity.NewResourceOracle(storage) @@ -49,31 +48,32 @@ func cap(ctx context.Context, client zbus.Client) { Msg("resource units found") log.Info().Msg("read DMI info") - dmi, err := r.DMI() - if err != nil { - log.Fatal().Err(err).Msgf("failed to read DMI information from hardware") - } - - disks, err := r.Disks() - if err != nil { - log.Fatal().Err(err).Msgf("failed to read smartctl information from disks") - } - - hypervisor, err := r.GetHypervisor() - if err != nil { - log.Fatal().Err(err).Msgf("failed to read virtualized state") - } - - ru := directory.ResourceAmount{ - Cru: resources.CRU, - Mru: float64(resources.MRU), - Hru: float64(resources.HRU), - Sru: float64(resources.SRU), - } + // dmi, err := r.DMI() + // if err != nil { + // log.Fatal().Err(err).Msgf("failed to read DMI information from hardware") + // } + + // disks, err := r.Disks() + // if err != nil { + // log.Fatal().Err(err).Msgf("failed to read smartctl information from disks") + // } + + // hypervisor, err := r.GetHypervisor() + // if err != nil { + // log.Fatal().Err(err).Msgf("failed to read virtualized state") + // } + + // ru := directory.ResourceAmount{ + // Cru: resources.CRU, + // Mru: float64(resources.MRU), + // Hru: float64(resources.HRU), + // Sru: float64(resources.SRU), + // } setCapacity := func() error { log.Info().Msg("sends capacity detail to BCDB") - return cl.NodeSetCapacity(nodeID, ru, *dmi, disks, hypervisor) + // return cl.NodeSetCapacity(nodeID, ru, *dmi, disks, hypervisor) + return nil } bo := backoff.NewExponentialBackOff() bo.MaxElapsedTime = 0 // retry forever diff --git a/cmds/identityd/main.go b/cmds/identityd/main.go index c06174b65..ba02e2573 100644 --- a/cmds/identityd/main.go +++ b/cmds/identityd/main.go @@ -13,14 +13,14 @@ import ( "github.com/jbenet/go-base58" "github.com/pkg/errors" + "github.com/threefoldtech/tfexplorer/client" + "github.com/threefoldtech/tfexplorer/models/generated/directory" "github.com/threefoldtech/zos/pkg/app" "github.com/threefoldtech/zos/pkg/flist" "github.com/threefoldtech/zos/pkg/geoip" "github.com/threefoldtech/zos/pkg/network" "github.com/threefoldtech/zos/pkg/stubs" "github.com/threefoldtech/zos/pkg/upgrade" - "github.com/threefoldtech/zos/tools/client" - "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" "github.com/cenkalti/backoff/v3" "github.com/threefoldtech/zos/pkg" diff --git a/cmds/networkd/addr_watcher.go b/cmds/networkd/addr_watcher.go index d144762b2..b75d56601 100644 --- a/cmds/networkd/addr_watcher.go +++ b/cmds/networkd/addr_watcher.go @@ -9,12 +9,12 @@ import ( "time" "github.com/cenkalti/backoff/v3" + "github.com/threefoldtech/tfexplorer/client" + "github.com/threefoldtech/tfexplorer/models/generated/directory" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/network/ifaceutil" "github.com/threefoldtech/zos/pkg/network/types" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/client" - "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" + "github.com/threefoldtech/tfexplorer/schema" "github.com/vishvananda/netlink" ) diff --git a/cmds/networkd/main.go b/cmds/networkd/main.go index 119aed395..fe24c41a6 100644 --- a/cmds/networkd/main.go +++ b/cmds/networkd/main.go @@ -9,6 +9,7 @@ import ( "github.com/cenkalti/backoff/v3" "github.com/rs/zerolog/log" + "github.com/threefoldtech/tfexplorer/client" "github.com/threefoldtech/zbus" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/app" @@ -19,7 +20,6 @@ import ( "github.com/threefoldtech/zos/pkg/stubs" "github.com/threefoldtech/zos/pkg/utils" "github.com/threefoldtech/zos/pkg/version" - "github.com/threefoldtech/zos/tools/client" ) const redisSocket = "unix:///var/run/redis.sock" diff --git a/cmds/networkd/pubiface.go b/cmds/networkd/pubiface.go index 33cc13dbb..50e098359 100644 --- a/cmds/networkd/pubiface.go +++ b/cmds/networkd/pubiface.go @@ -6,11 +6,11 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/threefoldtech/tfexplorer/client" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/network" "github.com/threefoldtech/zos/pkg/network/namespace" "github.com/threefoldtech/zos/pkg/network/types" - "github.com/threefoldtech/zos/tools/client" ) // ErrNoPubIface is the error returns by ReadPubIface when no public diff --git a/go.mod b/go.mod index ad7b84e85..2347214a1 100644 --- a/go.mod +++ b/go.mod @@ -4,83 +4,52 @@ go 1.13 require ( github.com/BurntSushi/toml v0.3.1 - github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 github.com/alexflint/go-filemutex v0.0.0-20171028004239-d358565f3c3f - github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect github.com/blang/semver v3.5.1+incompatible github.com/cenkalti/backoff/v3 v3.0.0 github.com/containerd/cgroups v0.0.0-20200327175542-b44481373989 github.com/containerd/containerd v1.3.2 - github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect - github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00 // indirect - github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c // indirect + github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb // indirect + github.com/containerd/ttrpc v1.0.0 // indirect github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd github.com/containernetworking/cni v0.7.2-0.20190807151350-8c6c47d1c7fc github.com/containernetworking/plugins v0.8.4 - github.com/dave/jennifer v1.3.0 github.com/deckarep/golang-set v1.7.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/emicklei/dot v0.10.1 github.com/firecracker-microvm/firecracker-go-sdk v0.19.1-0.20200110212531-741fc8cb0f2e github.com/fsnotify/fsnotify v1.4.7 github.com/gizak/termui/v3 v3.1.0 - github.com/go-ole/go-ole v1.2.4 // indirect - github.com/go-openapi/analysis v0.19.7 // indirect - github.com/go-openapi/errors v0.19.3 // indirect - github.com/go-openapi/runtime v0.19.9 // indirect - github.com/go-openapi/spec v0.19.5 // indirect - github.com/go-openapi/strfmt v0.19.4 // indirect - github.com/go-openapi/swag v0.19.6 // indirect - github.com/go-openapi/validate v0.19.5 // indirect github.com/go-redis/redis v6.15.5+incompatible - github.com/gogo/googleapis v1.3.0 // indirect - github.com/golang/protobuf v1.3.3 // indirect + github.com/gogo/googleapis v1.3.2 // indirect github.com/gomodule/redigo v2.0.0+incompatible github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/google/uuid v1.1.1 - github.com/gorilla/handlers v1.4.2 - github.com/gorilla/mux v1.7.4 - github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365 - github.com/imdario/mergo v0.3.8 // indirect github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6 - github.com/mattn/go-runewidth v0.0.4 // indirect - github.com/opencontainers/go-digest v1.0.0-rc1 // indirect - github.com/opencontainers/image-spec v1.0.1 // indirect - github.com/opencontainers/runc v0.1.1 // indirect github.com/opencontainers/runtime-spec v1.0.1 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.5.1 - github.com/rakyll/statik v0.1.7 github.com/rs/zerolog v1.18.0 - github.com/rusart/muxprom v0.0.0-20200323164249-36ea051efbe6 github.com/shirou/gopsutil v2.19.11+incompatible - github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect github.com/stellar/go v0.0.0-20200325172527-9cabbc6b9388 github.com/stretchr/testify v1.5.1 - github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 // indirect github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae + github.com/threefoldtech/tfexplorer v0.0.0-00010101000000-000000000000 github.com/threefoldtech/zbus v0.1.3 github.com/tyler-smith/go-bip39 v1.0.2 github.com/urfave/cli v1.22.3 github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df - github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/whs/nacl-sealed-box v0.0.0-20180930164530-92b9ba845d8d - github.com/xdg/stringprep v1.0.0 // indirect - github.com/zaibon/httpsig v0.0.0-20200401133919-ea9cb57b0946 - go.etcd.io/bbolt v1.3.3 // indirect - go.mongodb.org/mongo-driver v1.3.0 golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 - golang.org/x/net v0.0.0-20200301022130-244492dfa37a golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20191219145116-fa6499c8e75f - google.golang.org/appengine v1.6.5 // indirect - google.golang.org/grpc v1.24.0 // indirect gopkg.in/yaml.v2 v2.2.7 gotest.tools v2.2.0+incompatible ) replace github.com/docker/distribution v2.7.1+incompatible => github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible + +replace github.com/threefoldtech/tfgateway => ../tf_gateway + +replace github.com/threefoldtech/tfexplorer => ../tfexplorer diff --git a/go.sum b/go.sum index 1d424483c..9bceb0120 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bitbucket.org/ww/goautoneg v0.0.0-20120707110453-75cd24fc2f2c/go.mod h1:1vhO7Mn/FZMgOgDVGLy5X1mE6rq1HbkBdkF/yj8zkcg= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -6,6 +7,8 @@ code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48/go.mod h1:wN/zk firebase.google.com/go v3.12.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw= +github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U= github.com/Masterminds/squirrel v0.0.0-20161115235646-20f192218cf5/go.mod h1:xnKTFzjGUiZtiOagBsfnvomW+nJg2usB1ZpordQWqNM= github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= @@ -56,17 +59,22 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/containerd/cgroups v0.0.0-20190911145653-fc51bcbe4714/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20200327175542-b44481373989 h1:ziD9Q8QdO0vybP3fYg5MQ+PqDAb61jEbdCsL2f5xXK4= github.com/containerd/cgroups v0.0.0-20200327175542-b44481373989/go.mod h1:CStdkl05lBnJej94BPFoJ7vB8cELKXwViS+dgfW0/M8= github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb h1:nXPkFq8X1a9ycY3GYQpFNxHh3j2JgY7zDZfq2EXMIzk= +github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= github.com/containerd/fifo v0.0.0-20190816180239-bda0ff6ed73c/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00 h1:lsjC5ENBl+Zgf38+B0ymougXFp0BaubeIVETltYZTQw= github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c h1:+RqLdWzn0xFunb+sxXaEzHOg8NuEG/eaI+9C1xXX8Mw= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.0 h1:NY8Zk2i7TpkLxrkOASo+KTFq9iNCEmMH2/ZG9OuOw6k= +github.com/containerd/ttrpc v1.0.0/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd h1:bRLyitWw3PT/2YuVaCKTPg0cA5dOFKFwKtkfcP2dLsA= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containernetworking/cni v0.7.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= @@ -107,6 +115,7 @@ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6Uezg github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -238,13 +247,18 @@ github.com/gobuffalo/packr v1.12.1/go.mod h1:H2dZhQFqHeZwr/5A/uGQkBp7xYuMGuzXFeK github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c h1:RBUpb2b14UnmRHNd2uHz20ZHLDK+SW5Us/vWF5IHRaY= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.3.0 h1:M695OaDJ5ipWvDPcoAg/YL9c3uORAegkEfBqTQF/fTQ= github.com/gogo/googleapis v1.3.0/go.mod h1:d+q1s/xVJxZGKWwC/6UfPIF33J+G1Tq4GYv9Y+Tg/EU= +github.com/gogo/googleapis v1.3.2 h1:kX1es4djPJrsDhY7aZKJy7aZasdcB5oSOEphMjSB53c= +github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -406,9 +420,13 @@ github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= @@ -425,6 +443,7 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -482,6 +501,7 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -497,9 +517,11 @@ github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c h1:gqEdF4VwBu3lTKGH github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0= github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cobra v0.0.0-20160830174925-9c28e4bbd74e/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20161005214240-4bd69631f475/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v0.0.0-20150621231900-db7ff930a189/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/stellar/go v0.0.0-20200325172527-9cabbc6b9388 h1:3r/TV2k3JUMM5GzxpOclTH4NF4yYNqMjjV7PbOVt1Bg= @@ -522,8 +544,10 @@ github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae h1:vgGSvdW5Lqg+I1 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae/go.mod h1:quDq6Se6jlGwiIKia/itDZxqC5rj6/8OdFyMMAwTxCs= github.com/threefoldtech/zbus v0.1.3 h1:18DnIzximRbATle5ZdZz0i84n/bCYB8k/gkhr2dXayc= github.com/threefoldtech/zbus v0.1.3/go.mod h1:ZtiRpcqzEBJetVQDsEbw0p48h/AF3O1kf0tvd30I0BU= +github.com/threefoldtech/zos v0.2.3/go.mod h1:Fs/ckf/CkysugXBAXf5+zPxStH55FyJZWHJOJV4T1Sk= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tyler-smith/go-bip32 v0.0.0-20170922074101-2c9cfd177564/go.mod h1:0/YuQQF676+d4CMNclTqGUam1EDwz0B8o03K9pQqA3c= github.com/tyler-smith/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= @@ -576,7 +600,10 @@ go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.3.0 h1:ew6uUIeJOo+qdUUv7LxFCUhtWmVv7ZV/Xuy4FAUsw2E= go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.2 h1:IYppNjEV/C+/3VPbhHVxQ4t04eVW0cLp0/pNdW++6Ug= +go.mongodb.org/mongo-driver v1.3.2/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -643,6 +670,7 @@ golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/pkg/app/boot.go b/pkg/app/boot.go index b35d2ebf0..4ee638177 100644 --- a/pkg/app/boot.go +++ b/pkg/app/boot.go @@ -5,23 +5,23 @@ import ( "path/filepath" ) -const bootedPath = "/var/run/modules" +var BootedPath = "/var/run/modules" // MarkBooted creates a file in a memory // this file then can be used to check if "something" has been restared // if its the first time it starts func MarkBooted(name string) error { - if err := os.MkdirAll(bootedPath, 0770); err != nil { + if err := os.MkdirAll(BootedPath, 0770); err != nil { return err } - path := filepath.Join(bootedPath, name) + path := filepath.Join(BootedPath, name) _, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE|os.O_TRUNC|os.O_EXCL, 0666) return err } // IsFirstBoot checks if the a file has been created by MarkBooted function func IsFirstBoot(name string) bool { - path := filepath.Join(bootedPath, name) + path := filepath.Join(BootedPath, name) _, err := os.Stat(path) return !(err == nil) } diff --git a/pkg/app/explorer.go b/pkg/app/explorer.go index 86b3180be..f1522a315 100644 --- a/pkg/app/explorer.go +++ b/pkg/app/explorer.go @@ -4,9 +4,9 @@ import ( "crypto/ed25519" "github.com/pkg/errors" + "github.com/threefoldtech/tfexplorer/client" "github.com/threefoldtech/zos/pkg/environment" "github.com/threefoldtech/zos/pkg/identity" - "github.com/threefoldtech/zos/tools/client" ) const seedPath = "/var/cache/modules/identityd/seed.txt" diff --git a/pkg/network/networker.go b/pkg/network/networker.go index af5c44c80..0e62bce09 100644 --- a/pkg/network/networker.go +++ b/pkg/network/networker.go @@ -13,10 +13,10 @@ import ( "github.com/termie/go-shutil" + "github.com/threefoldtech/tfexplorer/client" "github.com/threefoldtech/zos/pkg/cache" "github.com/threefoldtech/zos/pkg/network/ndmz" "github.com/threefoldtech/zos/pkg/network/tuntap" - "github.com/threefoldtech/zos/tools/client" "github.com/containernetworking/plugins/pkg/ns" "github.com/vishvananda/netlink" diff --git a/pkg/network/types/types.go b/pkg/network/types/types.go index 33141318f..83cfdb401 100644 --- a/pkg/network/types/types.go +++ b/pkg/network/types/types.go @@ -4,9 +4,9 @@ import ( "fmt" "net" - "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" + "github.com/threefoldtech/tfexplorer/models/generated/directory" - "github.com/threefoldtech/zos/pkg/schema" + "github.com/threefoldtech/tfexplorer/schema" ) // IfaceType define the different public interface supported diff --git a/pkg/network/types/types_test.go b/pkg/network/types/types_test.go index f2e87bc96..288afefd6 100644 --- a/pkg/network/types/types_test.go +++ b/pkg/network/types/types_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" + "github.com/threefoldtech/tfexplorer/models/generated/directory" + "github.com/threefoldtech/tfexplorer/schema" ) func TestParseIPNet(t *testing.T) { diff --git a/pkg/provision/converter.go b/pkg/provision/converter.go index 4d16843d6..587127185 100644 --- a/pkg/provision/converter.go +++ b/pkg/provision/converter.go @@ -6,11 +6,11 @@ import ( "net" "time" + "github.com/threefoldtech/tfexplorer/models/generated/workloads" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/container/logger" "github.com/threefoldtech/zos/pkg/container/stats" "github.com/threefoldtech/zos/pkg/network/types" - "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" ) // ContainerToProvisionType converts TfgridReservationContainer1 to Container diff --git a/pkg/provision/converter_test.go b/pkg/provision/converter_test.go index f849b8a99..7e6e2f1cb 100644 --- a/pkg/provision/converter_test.go +++ b/pkg/provision/converter_test.go @@ -5,8 +5,8 @@ import ( "net" "testing" + "github.com/threefoldtech/tfexplorer/models/generated/workloads" "github.com/threefoldtech/zos/pkg/network/types" - "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" "github.com/threefoldtech/zos/pkg" @@ -14,7 +14,7 @@ import ( "github.com/threefoldtech/zos/pkg/container/logger" "github.com/threefoldtech/zos/pkg/container/stats" "github.com/threefoldtech/zos/pkg/provision" - schema "github.com/threefoldtech/zos/pkg/schema" + schema "github.com/threefoldtech/tfexplorer/schema" "gotest.tools/assert" ) diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go index 30527dd6b..6f48674a0 100644 --- a/pkg/provision/engine.go +++ b/pkg/provision/engine.go @@ -7,10 +7,10 @@ import ( "fmt" "time" + "github.com/threefoldtech/tfexplorer/client" + "github.com/threefoldtech/tfexplorer/models/generated/directory" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/stubs" - "github.com/threefoldtech/zos/tools/client" - "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" "github.com/pkg/errors" "github.com/rs/zerolog/log" diff --git a/pkg/provision/owner_cache.go b/pkg/provision/owner_cache.go index f5517c874..926a8ec9f 100644 --- a/pkg/provision/owner_cache.go +++ b/pkg/provision/owner_cache.go @@ -3,7 +3,7 @@ package provision import ( "fmt" - "github.com/threefoldtech/zos/tools/client" + "github.com/threefoldtech/tfexplorer/client" ) // ReservationGetter define the interface how to get diff --git a/pkg/provision/reservation.go b/pkg/provision/reservation.go index 73383eb8e..f63e5a7df 100644 --- a/pkg/provision/reservation.go +++ b/pkg/provision/reservation.go @@ -9,12 +9,12 @@ import ( "time" "github.com/pkg/errors" + "github.com/threefoldtech/tfexplorer/models/generated/workloads" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/container/logger" "github.com/threefoldtech/zos/pkg/container/stats" - "github.com/threefoldtech/zos/pkg/schema" + "github.com/threefoldtech/tfexplorer/schema" "github.com/threefoldtech/zos/pkg/versioned" - "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" ) // ReservationType type @@ -33,8 +33,6 @@ const ( DebugReservation ReservationType = "debug" // KubernetesReservation type KubernetesReservation ReservationType = "kubernetes" - // TCPProxyReservation type - TCPProxyReservation ReservationType = "tc-proxy" ) var ( diff --git a/pkg/provision/source.go b/pkg/provision/source.go index 4f5451b78..72a18a1da 100644 --- a/pkg/provision/source.go +++ b/pkg/provision/source.go @@ -10,8 +10,8 @@ import ( "time" "github.com/rs/zerolog/log" + "github.com/threefoldtech/tfexplorer/client" "github.com/threefoldtech/zos/pkg" - "github.com/threefoldtech/zos/tools/client" ) type pollSource struct { diff --git a/pkg/schema/README.md b/pkg/schema/README.md deleted file mode 100644 index 371d1b660..000000000 --- a/pkg/schema/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# About -Go library to work with jxs schema from Go. The library basically does two things -- Implement all the native schema/jsx types (string, numeric, date, etc...) -- Generate Go types from schema files. The types later can be used to do calls to services that expect schema objects. - -# Reference -Schema docs is defined [here](https://github.com/threefoldtech/jumpscaleX/blob/master/docs/schemas/schemas.md). - -## How to use -Please check example [here](generate_test.go) \ No newline at end of file diff --git a/pkg/schema/generate.go b/pkg/schema/generate.go deleted file mode 100644 index 0e01fd6fe..000000000 --- a/pkg/schema/generate.go +++ /dev/null @@ -1,216 +0,0 @@ -package schema - -import ( - "encoding/json" - "fmt" - "io" - "net/url" - "strings" - - "github.com/dave/jennifer/jen" - "github.com/iancoleman/strcase" - "github.com/pkg/errors" -) - -const ( - // SchemaQual path to schema package - SchemaQual = "github.com/threefoldtech/zos/pkg/schema" -) - -var ( - // this is only the list of the types implemented so far - // in go, more types can be later added to schema modules - // and then mapped here to support it. - goKindMap = map[Kind][2]string{ - StringKind: {"", "string"}, - IntegerKind: {"", "int64"}, - FloatKind: {"", "float64"}, - BoolKind: {"", "bool"}, - BytesKind: {"", "[]byte"}, - DateKind: {SchemaQual, "Date"}, - DateTimeKind: {SchemaQual, "Date"}, - NumericKind: {SchemaQual, "Numeric"}, - IPAddressKind: {"net", "IP"}, - IPRangeKind: {SchemaQual, "IPRange"}, - EmailKind: {SchemaQual, "Email"}, - //TODO add other types here (for example, Email, Phone, etc..) - } -) - -// GenerateGolang generate type stubs for Go from schema -func GenerateGolang(w io.Writer, pkg string, schema Schema) error { - g := goGenerator{enums: make(map[string]Type)} - return g.Generate(w, pkg, schema) -} - -// just a namespace for generation methods -type goGenerator struct { - //enums: because enums types are defined on the property itself - //and not elsewere (unlike object types for example) they need - //to added dynamically to this map while generating code, then - //processed later, to generate the corresponding type - enums map[string]Type -} - -func (g *goGenerator) nameFromURL(u string) (string, error) { - p, err := url.Parse(u) - - if err != nil { - return "", err - } - u = strings.ReplaceAll(p.Path, "/", "_") - u = strings.ReplaceAll(u, ".", "_") - return strcase.ToCamel(u), nil -} - -func (g *goGenerator) Generate(w io.Writer, pkg string, schema Schema) error { - j := jen.NewFile(pkg) - - for _, obj := range schema { - if err := g.object(j, obj); err != nil { - return err - } - - j.Line() - - } - - for name, typ := range g.enums { - if err := g.enum(j, name, &typ); err != nil { - return err - } - - j.Line() - } - - return j.Render(w) -} - -func (g *goGenerator) enumValues(typ *Type) []string { - var values string - if err := json.Unmarshal([]byte(typ.Default), &values); err != nil { - panic(fmt.Errorf("failed to parse enum values: `%s`: %s", typ.Default, err)) - } - return strings.Split(values, ",") -} - -func (g *goGenerator) enum(j *jen.File, name string, typ *Type) error { - typName := fmt.Sprintf("%sEnum", name) - j.Type().Id(typName).Id("uint8").Line() - j.Const().DefsFunc(func(group *jen.Group) { - for i, value := range g.enumValues(typ) { - n := fmt.Sprintf("%s%s", name, strcase.ToCamel(strings.TrimSpace(value))) - if i == 0 { - group.Id(n).Id(typName).Op("=").Iota() - } else { - group.Id(n) - } - } - }).Line() - - j.Func().Params(jen.Id("e").Id(typName)).Id("String").Params().Id("string").BlockFunc(func(group *jen.Group) { - group.Switch(jen.Id("e")).BlockFunc(func(group *jen.Group) { - for _, value := range g.enumValues(typ) { - value = strings.TrimSpace(value) - n := fmt.Sprintf("%s%s", name, strcase.ToCamel(value)) - group.Case(jen.Id(n)).Block(jen.Return(jen.Lit(value))) - } - }) - - group.Return(jen.Lit("UNKNOWN")) - }).Line() - - return nil -} - -func (g *goGenerator) renderType(typ *Type) (jen.Code, error) { - if typ == nil { - return jen.Interface(), nil - } - - switch typ.Kind { - case DictKind: - // Dicts in jumpscale do not provide an "element" type - // In that case (if element is not provided) a map of interface{} is - // used. If an element is provided, this element will be used instead - elem, err := g.renderType(typ.Element) - if err != nil { - return nil, err - } - return jen.Map(jen.Id("string")).Add(elem), nil - case ListKind: - elem, err := g.renderType(typ.Element) - if err != nil { - return nil, err - } - return jen.Op("[]").Add(elem), nil - case ObjectKind: - // we assume that object name is defined later in the file - o, err := g.nameFromURL(typ.Reference) - if err != nil { - return nil, err - } - return jen.Qual("", o), nil - default: - m, ok := goKindMap[typ.Kind] - if !ok { - return nil, errors.Errorf("unsupported type in the go generator: %s", typ.Kind) - } - - return jen.Qual(m[0], m[1]), nil - } -} - -func (g *goGenerator) object(j *jen.File, obj *Object) error { - structName, err := g.nameFromURL(obj.URL) - if err != nil { - return err - } - var structErr error - j.Type().Id(structName).StructFunc(func(group *jen.Group) { - if obj.IsRoot { - group.Id("ID").Add(jen.Qual(SchemaQual, "ID")).Tag(map[string]string{"json": "id", "bson": "_id"}) - } - for _, prop := range obj.Properties { - name := strcase.ToCamel(prop.Name) - - var typ jen.Code - if prop.Type.Kind == EnumKind { - // enums are handled differently than any other - // type because they are not defined elsewere. - // except on the property itself. so we need - // to generate a totally new type for it. - // TODO: do we need to have a fqn for the enum ObjectPropertyEnum instead? - enumName := structName + name - enumTypName := fmt.Sprintf("%sEnum", enumName) - g.enums[enumName] = prop.Type - typ = jen.Id(enumTypName) - } else { - typ, err = g.renderType(&prop.Type) - if err != nil { - structErr = errors.Wrapf(err, "object(%s).property(%s)", obj.URL, name) - return - } - } - - group.Id(name).Add(typ).Tag(map[string]string{"json": prop.Name, "bson": prop.Name}) - } - }) - - // add the new Method! - j.Func().Id(fmt.Sprintf("New%s", structName)).Params().Params(jen.Id(structName), jen.Id("error")).BlockFunc(func(group *jen.Group) { - group.Const().Id("value").Op("=").Lit(obj.Default()) - group.Var().Id("object").Id(structName) - - group.If(jen.Id("err").Op(":=").Qual("encoding/json", "Unmarshal").Call( - jen.Op("[]").Id("byte").Parens(jen.Id("value")), - jen.Op("&").Id("object"), - ), jen.Id("err").Op("!=").Nil()).Block( - jen.Return(jen.Id("object"), jen.Id("err")), - ) - - group.Return(jen.Id("object"), jen.Nil()) - }) - - return structErr -} diff --git a/pkg/schema/generate_test.go b/pkg/schema/generate_test.go deleted file mode 100644 index 379f4b642..000000000 --- a/pkg/schema/generate_test.go +++ /dev/null @@ -1,333 +0,0 @@ -package schema - -import ( - "os" - "strings" -) - -func ExampleGenerateGolang() { - const input = ` -@url = jumpscale.digitalme.package -name = "UNKNOWN" (S) #official name of the package, there can be no overlap (can be dot notation) -enable = true (B) -numerics = (LN) -args = (LO) !jumpscale.digitalme.package.arg -loaders= (LO) !jumpscale.digitalme.package.loader - -@url = jumpscale.digitalme.package.arg -key = "" (S) -val = "" (S) - -@url = jumpscale.digitalme.package.loader -giturl = (S) -dest = (S) -enable = true (B) -creation = (D) - ` - - schema, err := New(strings.NewReader(input)) - if err != nil { - panic(err) - } - - if err := GenerateGolang(os.Stdout, "test", schema); err != nil { - panic(err) - } - - // Output: - // package test - // - // import ( - // "encoding/json" - // schema "github.com/threefoldtech/zos/pkg/schema" - // ) - // - // type JumpscaleDigitalmePackage struct { - // ID schema.ID `bson:"_id" json:"id"` - // Name string `bson:"name" json:"name"` - // Enable bool `bson:"enable" json:"enable"` - // Numerics []schema.Numeric `bson:"numerics" json:"numerics"` - // Args []JumpscaleDigitalmePackageArg `bson:"args" json:"args"` - // Loaders []JumpscaleDigitalmePackageLoader `bson:"loaders" json:"loaders"` - // } - // - // func NewJumpscaleDigitalmePackage() (JumpscaleDigitalmePackage, error) { - // const value = "{\"name\": \"UNKNOWN\", \"enable\": true}" - // var object JumpscaleDigitalmePackage - // if err := json.Unmarshal([]byte(value), &object); err != nil { - // return object, err - // } - // return object, nil - // } - // - // type JumpscaleDigitalmePackageArg struct { - // Key string `bson:"key" json:"key"` - // Val string `bson:"val" json:"val"` - // } - // - // func NewJumpscaleDigitalmePackageArg() (JumpscaleDigitalmePackageArg, error) { - // const value = "{\"key\": \"\", \"val\": \"\"}" - // var object JumpscaleDigitalmePackageArg - // if err := json.Unmarshal([]byte(value), &object); err != nil { - // return object, err - // } - // return object, nil - // } - // - // type JumpscaleDigitalmePackageLoader struct { - // Giturl string `bson:"giturl" json:"giturl"` - // Dest string `bson:"dest" json:"dest"` - // Enable bool `bson:"enable" json:"enable"` - // Creation schema.Date `bson:"creation" json:"creation"` - // } - // - // func NewJumpscaleDigitalmePackageLoader() (JumpscaleDigitalmePackageLoader, error) { - // const value = "{\"enable\": true}" - // var object JumpscaleDigitalmePackageLoader - // if err := json.Unmarshal([]byte(value), &object); err != nil { - // return object, err - // } - // return object, nil - // } - -} - -func ExampleGenerateGolang_enums() { - const input = ` -@url = person -name = "UNKNOWN" (S) #official name of the package, there can be no overlap (can be dot notation) -gender = "male,female,others" (E) - ` - - schema, err := New(strings.NewReader(input)) - if err != nil { - panic(err) - } - - if err := GenerateGolang(os.Stdout, "test", schema); err != nil { - panic(err) - } - - // Output: - // package test - // - // import ( - // "encoding/json" - // schema "github.com/threefoldtech/zos/pkg/schema" - // ) - // - // type Person struct { - // ID schema.ID `bson:"_id" json:"id"` - // Name string `bson:"name" json:"name"` - // Gender PersonGenderEnum `bson:"gender" json:"gender"` - // } - // - // func NewPerson() (Person, error) { - // const value = "{\"name\": \"UNKNOWN\"}" - // var object Person - // if err := json.Unmarshal([]byte(value), &object); err != nil { - // return object, err - // } - // return object, nil - // } - // - // type PersonGenderEnum uint8 - // - // const ( - // PersonGenderMale PersonGenderEnum = iota - // PersonGenderFemale - // PersonGenderOthers - // ) - // - // func (e PersonGenderEnum) String() string { - // switch e { - // case PersonGenderMale: - // return "male" - // case PersonGenderFemale: - // return "female" - // case PersonGenderOthers: - // return "others" - // } - // return "UNKNOWN" - // } - -} - -func ExampleGenerateGolang_enums2() { - const input = ` - -@url = tfgrid.node.resource.price.1 -cru = (F) - -mru = (F) -hru = (F) -sru = (F) -nru = (F) -currency = "EUR,USD,TFT,AED,GBP" (E) - ` - - schema, err := New(strings.NewReader(input)) - if err != nil { - panic(err) - } - - if err := GenerateGolang(os.Stdout, "test", schema); err != nil { - panic(err) - } - - // Output: - // package test - // - // import ( - // "encoding/json" - // schema "github.com/threefoldtech/zos/pkg/schema" - // ) - // - // type TfgridNodeResourcePrice1 struct { - // ID schema.ID `bson:"_id" json:"id"` - // Cru float64 `bson:"cru" json:"cru"` - // Mru float64 `bson:"mru" json:"mru"` - // Hru float64 `bson:"hru" json:"hru"` - // Sru float64 `bson:"sru" json:"sru"` - // Nru float64 `bson:"nru" json:"nru"` - // Currency TfgridNodeResourcePrice1CurrencyEnum `bson:"currency" json:"currency"` - // } - // - // func NewTfgridNodeResourcePrice1() (TfgridNodeResourcePrice1, error) { - // const value = "{}" - // var object TfgridNodeResourcePrice1 - // if err := json.Unmarshal([]byte(value), &object); err != nil { - // return object, err - // } - // return object, nil - // } - // - // type TfgridNodeResourcePrice1CurrencyEnum uint8 - // - // const ( - // TfgridNodeResourcePrice1CurrencyEUR TfgridNodeResourcePrice1CurrencyEnum = iota - // TfgridNodeResourcePrice1CurrencyUSD - // TfgridNodeResourcePrice1CurrencyTFT - // TfgridNodeResourcePrice1CurrencyAED - // TfgridNodeResourcePrice1CurrencyGBP - // ) - // - // func (e TfgridNodeResourcePrice1CurrencyEnum) String() string { - // switch e { - // case TfgridNodeResourcePrice1CurrencyEUR: - // return "EUR" - // case TfgridNodeResourcePrice1CurrencyUSD: - // return "USD" - // case TfgridNodeResourcePrice1CurrencyTFT: - // return "TFT" - // case TfgridNodeResourcePrice1CurrencyAED: - // return "AED" - // case TfgridNodeResourcePrice1CurrencyGBP: - // return "GBP" - // } - // return "UNKNOWN" - // } -} - -func ExampleGenerateGolang_ip() { - const input = ` -@url = network -name = "UNKNOWN" (S) #official name of the package, there can be no overlap (can be dot notation) -ip = "172.0.0.1" (ipaddr) -net = "2001:db8::/32" (iprange) -addresses = (Lipaddr) - ` - - schema, err := New(strings.NewReader(input)) - if err != nil { - panic(err) - } - - if err := GenerateGolang(os.Stdout, "test", schema); err != nil { - panic(err) - } - - // Output: - // package test - // - // import ( - // "encoding/json" - // schema "github.com/threefoldtech/zos/pkg/schema" - // "net" - // ) - // - // type Network struct { - // ID schema.ID `bson:"_id" json:"id"` - // Name string `bson:"name" json:"name"` - // Ip net.IP `bson:"ip" json:"ip"` - // Net schema.IPRange `bson:"net" json:"net"` - // Addresses []net.IP `bson:"addresses" json:"addresses"` - // } - // - // func NewNetwork() (Network, error) { - // const value = "{\"name\": \"UNKNOWN\", \"ip\": \"172.0.0.1\", \"net\": \"2001:db8::/32\"}" - // var object Network - // if err := json.Unmarshal([]byte(value), &object); err != nil { - // return object, err - // } - // return object, nil - // } -} - -func ExampleGenerateGolang_dict() { - const input = ` -@url = parent -name = (S) #official name of the package, there can be no overlap (can be dot notation) -data = (dictO) ! child # dict of children -tags = (M) # dict with no defined object type - -@url = child -name = (S) - ` - - schema, err := New(strings.NewReader(input)) - if err != nil { - panic(err) - } - - if err := GenerateGolang(os.Stdout, "test", schema); err != nil { - panic(err) - } - - // Output: - // package test - // - // import ( - // "encoding/json" - // schema "github.com/threefoldtech/zos/pkg/schema" - // ) - // - // type Parent struct { - // ID schema.ID `bson:"_id" json:"id"` - // Name string `bson:"name" json:"name"` - // Data map[string]Child `bson:"data" json:"data"` - // Tags map[string]interface{} `bson:"tags" json:"tags"` - // } - // - // func NewParent() (Parent, error) { - // const value = "{}" - // var object Parent - // if err := json.Unmarshal([]byte(value), &object); err != nil { - // return object, err - // } - // return object, nil - // } - // - // type Child struct { - // Name string `bson:"name" json:"name"` - // } - // - // func NewChild() (Child, error) { - // const value = "{}" - // var object Child - // if err := json.Unmarshal([]byte(value), &object); err != nil { - // return object, err - // } - // return object, nil - // } -} diff --git a/pkg/schema/parse.go b/pkg/schema/parse.go deleted file mode 100644 index 1efbf67b6..000000000 --- a/pkg/schema/parse.go +++ /dev/null @@ -1,383 +0,0 @@ -package schema - -import ( - "bufio" - "fmt" - "io" - "regexp" - "strings" - "unicode" -) - -const ( - // URL directive - URL = "url" -) - -var ( - directiveRe = regexp.MustCompile(`@(\w+)\s*=\s*([^\#]+)`) - propertyRe = regexp.MustCompile(`(\w+)(\*){0,2}\s*=\s*([^\#]+)`) - typeRe = regexp.MustCompile(`^([^\(]*)\(([^\)]+)\)\s*(?:!(.+))?$`) -) - -// Schema is a container for all objects defined in the source -type Schema []*Object - -// Object defines a schema object -type Object struct { - URL string - IsRoot bool - Directives []Directive - Properties []Property -} - -func (o *Object) listDefault(buf *strings.Builder, typ *Type) { - // list can be in format [o1, o2], or "o1, o2" - // we need to figure out the type, and try to generate - // a valid json representation of the list - - def := typ.Default - if def[0] == '[' { - // we assume it's a valid list - buf.WriteString(def) - return - } - // cut the " or the ' at the ends of the array - cuttest := "\"" - if def[0] == '\'' { - cuttest = "'" - } - def = strings.Trim(def, cuttest) - buf.WriteRune('[') - buf.WriteString(def) - buf.WriteByte(']') -} - -//Default generates a json string that holds the default value -//for the object -func (o *Object) Default() string { - var buf strings.Builder - buf.WriteRune('{') - - for _, prop := range o.Properties { - if len(prop.Type.Default) == 0 || prop.Type.Kind == EnumKind { - // if default is not set OR if enum type, we don't set a default - // Note: we ignore enum type because enums always have defaults - // set to 0 (first item in the enum type) - continue - } - - if buf.Len() > 1 { // the 1 is the { - buf.WriteString(", ") - } - - buf.WriteString(fmt.Sprintf(`"%s": `, prop.Name)) - if prop.Type.Kind == ListKind { - o.listDefault(&buf, &prop.Type) - } else { - buf.WriteString(prop.Type.Default) - } - } - - buf.WriteRune('}') - - return buf.String() -} - -// New reads the schema and return schema description objects -func New(r io.Reader) (schema Schema, err error) { - scanner := bufio.NewScanner(r) - var current *Object - nr := 0 - for scanner.Scan() { - nr++ - line := strings.TrimSpace(scanner.Text()) - - if len(line) == 0 { - continue - } - - switch line[0] { - case '#': - //comment line, we can skip it - continue - case '@': - //directive line, need to be processed and acted upon - dir, err := directive(line) - if err != nil { - return schema, err - } - - if dir.Key == URL { - //push current - if current != nil { - schema = append(schema, current) - } - current = &Object{ - URL: dir.Value, - } - } else if current == nil { - //directive out of scoop! - return schema, fmt.Errorf("unexpected directive [line %d]: %s", nr, dir) - } else { - current.Directives = append(current.Directives, dir) - } - default: - if current == nil { - return schema, fmt.Errorf("unexpected token [line %d]: %s", nr, line) - } - prop, err := property(line) - if err != nil { - return schema, fmt.Errorf("failed to parse property [line %d]: %s", nr, err) - } - - current.Properties = append(current.Properties, prop) - } - } - - if current != nil { - schema = append(schema, current) - } - - // mark roots -root: - for i := 0; i < len(schema); i++ { - a := schema[i] - for j := 0; j < len(schema); j++ { - b := schema[j] - for _, prop := range b.Properties { - var ref string - switch prop.Type.Kind { - case ObjectKind: - ref = prop.Type.Reference - case ListKind: - fallthrough - case DictKind: - if prop.Type.Element != nil { - ref = prop.Type.Element.Reference - } - } - - if a.URL == ref { - continue root - } - } - } - - a.IsRoot = true - } - - return -} - -// Directive is a piece of information attached to a schema object -type Directive struct { - Key string - Value string -} - -func (d *Directive) String() string { - return fmt.Sprintf("@%s = %s", d.Key, d.Value) -} - -func directive(line string) (dir Directive, err error) { - m := directiveRe.FindStringSubmatch(line) - if m == nil { - return dir, fmt.Errorf("invalid directive line") - } - - return Directive{Key: strings.ToLower(m[1]), Value: strings.TrimSpace(m[2])}, nil -} - -// Property defines a schema property -type Property struct { - Name string - Indexed bool - Type Type -} - -func property(line string) (property Property, err error) { - m := propertyRe.FindStringSubmatch(line) - if m == nil { - return property, fmt.Errorf("invalid property definition") - } - - property.Name = m[1] - property.Indexed = m[2] == "*" - typ, err := typeDef(strings.TrimSpace(m[3])) - if err != nil { - return property, nil - } - property.Type = *typ - - return -} - -// Kind defines the kind of a type -type Kind int - -// List of all the supported JSX types -const ( - UnknownKind Kind = iota - IntegerKind - FloatKind - BoolKind - StringKind - MobileKind - EmailKind - IPPortKind - IPAddressKind - IPRangeKind - DateKind - DateTimeKind - NumericKind - GUIDKind - DictKind - ListKind - ObjectKind - YamlKind - MultilineKind - HashKind - BytesKind - PercentKind - URLKind - EnumKind -) - -func (k Kind) String() string { - for s, i := range symbols { - if i == k { - return s - } - } - - return "unknown" -} - -var ( - symbols = map[string]Kind{ - // short symbols, they must be upper case - "S": StringKind, - "I": IntegerKind, - "F": FloatKind, - "B": BoolKind, - "D": DateKind, - "T": DateTimeKind, - "N": NumericKind, - "L": ListKind, - "O": ObjectKind, - "P": PercentKind, - "U": URLKind, - "H": HashKind, - "E": EnumKind, - "M": DictKind, // Map - - // long symbols, they must be lower case - "str": StringKind, - "string": StringKind, - "int": IntegerKind, - "integer": IntegerKind, - "float": FloatKind, - "bool": BoolKind, - "mobile": MobileKind, - "email": EmailKind, - "ipport": IPPortKind, - "ipaddr": IPAddressKind, - "ipaddress": IPAddressKind, - "iprange": IPRangeKind, - "date": DateKind, - "time": DateTimeKind, - "numeric": NumericKind, - "guid": GUIDKind, - "dict": DictKind, - "map": DictKind, - "yaml": YamlKind, - "multiline": MultilineKind, - "hash": HashKind, - "bin": BytesKind, - "bytes": BytesKind, - "percent": PercentKind, - "url": URLKind, - "object": ObjectKind, - "enum": EnumKind, - } -) - -// Type holds type information for a property -type Type struct { - Kind Kind - Default string - Reference string - Element *Type -} - -func tokenSplit(data []byte, atEOF bool) (advance int, token []byte, err error) { - if len(data) == 0 && atEOF { - return 0, nil, nil - } - - if unicode.IsUpper(rune(data[0])) { - return 1, data[0:1], nil - } - - var i int - for i = 0; i < len(data) && unicode.IsLower(rune(data[i])); i++ { - - } - - if i < len(data) { - // we hit an upper case char - return i, data[0:i], nil - } - - // we didn't hit eof yet. it means we can still continue - // scanning and fine more lower case letters - if !atEOF { - return 0, nil, nil - } - - // otherwise we return the whole thing - return len(data), data, nil -} - -func typeDef(def string) (*Type, error) { - // typeDef parses a type text and create a solid type definition - // current default values is ignored! - // (Type) - - m := typeRe.FindStringSubmatch(def) - if m == nil { - return nil, fmt.Errorf("invalid type definition") - } - - var root Type - root.Default = strings.TrimSpace(m[1]) - typeStr := m[2] - reference := strings.TrimSpace(m[3]) - - scanner := bufio.NewScanner(strings.NewReader(typeStr)) - scanner.Split(tokenSplit) - - current := &root - - for scanner.Scan() { - if current.Kind != UnknownKind { - current.Element = new(Type) - current = current.Element - } - - symbol := scanner.Text() - kind, ok := symbols[symbol] - if !ok { - return nil, fmt.Errorf("unknown kind: '%s'", symbol) - } - - current.Kind = kind - if current.Kind == ObjectKind { - current.Reference = reference - } - - } - - return &root, nil -} diff --git a/pkg/schema/parse_test.go b/pkg/schema/parse_test.go deleted file mode 100644 index 8c81a4a62..000000000 --- a/pkg/schema/parse_test.go +++ /dev/null @@ -1,374 +0,0 @@ -package schema - -import ( - "bufio" - "encoding/json" - "fmt" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestDirective(t *testing.T) { - dir, err := directive("@url = hello world # comment") - - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Equal(t, Directive{Key: "url", Value: "hello world"}, dir); !ok { - t.Error() - } - - dir, err = directive("@url hello world # comment") - - if ok := assert.Error(t, err); !ok { - t.Fatal() - } -} - -func TestProperty(t *testing.T) { - prop, err := property(`something* = "hello world" (S) # comment`) - - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Equal(t, Property{ - Name: "something", - Indexed: true, - Type: Type{ - Default: `"hello world"`, - Kind: StringKind, - }, - }, prop); !ok { - t.Error() - } - - prop, err = property("wrong hello world # comment") - - if ok := assert.Error(t, err); !ok { - t.Fatal() - } -} - -func TestPropertyEnum(t *testing.T) { - prop, err := property(`currency = "EUR,USD,TFT,AED,GBP" (E) # comment`) - - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Equal(t, Property{ - Name: "currency", - Indexed: false, - Type: Type{ - Default: `"EUR,USD,TFT,AED,GBP"`, - Kind: EnumKind, - }, - }, prop); !ok { - t.Error() - } - - prop, err = property("wrong hello world # comment") - - if ok := assert.Error(t, err); !ok { - t.Fatal() - } -} - -func TestTypeDef(t *testing.T) { - cases := []struct { - Input string - Output *Type - }{ - { - `"hello world" (S)`, - &Type{ - Default: `"hello world"`, - Kind: StringKind, - }, - }, - { - `(LO) ! reference.to.another.object`, - &Type{ - Kind: ListKind, - Element: &Type{ - Kind: ObjectKind, - Reference: "reference.to.another.object", - }, - }, - }, - { - `(Lmultiline) ! reference.to.another.object`, - &Type{ - Kind: ListKind, - Element: &Type{ - Kind: MultilineKind, - }, - }, - }, - { - `"1,2,3" (LI)`, - &Type{ - Kind: ListKind, - Default: `"1,2,3"`, - Element: &Type{ - Kind: IntegerKind, - }, - }, - }, - } - - for _, c := range cases { - t.Run(c.Input, func(t *testing.T) { - typ, err := typeDef(c.Input) - - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.EqualValues(t, c.Output, typ); !ok { - t.Error() - } - }) - } - - failures := []string{ - "(i)", "(Iunknown)", `"default" (S) garbage`, - } - - for _, c := range failures { - _, err := typeDef(c) - - if ok := assert.Error(t, err); !ok { - t.Error() - } - } -} - -func TestTokenScan(t *testing.T) { - cases := []struct { - Input string - Output []string - }{ - {"I", []string{"I"}}, - {"II", []string{"I", "I"}}, - {"Lstring", []string{"L", "string"}}, - {"LstringI", []string{"L", "string", "I"}}, - {"LO", []string{"L", "O"}}, - {"", nil}, - } - - for _, c := range cases { - t.Run(fmt.Sprintf("case.%s", c.Input), func(t *testing.T) { - scanner := bufio.NewScanner(strings.NewReader(c.Input)) - scanner.Split(tokenSplit) - - var out []string - for scanner.Scan() { - out = append(out, scanner.Text()) - } - - if ok := assert.Equal(t, c.Output, out); !ok { - t.Error() - } - }) - } - -} - -func TestSimpleNew(t *testing.T) { - const input = ` -# this is a comment - -@url = jumpscale.digitalme.package -name = "UNKNOWN" (S) #official name of the package, there can be no overlap (can be dot notation) -enable = true (B) -args = (LO) !jumpscale.digitalme.package.arg -loaders= (LO) !jumpscale.digitalme.package.loader - ` - - schema, err := New(strings.NewReader(input)) - - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Len(t, schema, 1); !ok { - t.Fatal() - } - - if ok := assert.Equal(t, &Object{ - URL: "jumpscale.digitalme.package", - IsRoot: true, - Properties: []Property{ - {Name: "name", Type: Type{ - Default: `"UNKNOWN"`, - Kind: StringKind, - }}, - {Name: "enable", Type: Type{ - Default: `true`, - Kind: BoolKind, - }}, - {Name: "args", Type: Type{ - Kind: ListKind, - Element: &Type{ - Kind: ObjectKind, - Reference: "jumpscale.digitalme.package.arg", - }, - }}, - {Name: "loaders", Type: Type{ - Kind: ListKind, - Element: &Type{ - Kind: ObjectKind, - Reference: "jumpscale.digitalme.package.loader", - }, - }}, - }, - }, schema[0]); !ok { - t.Error() - } -} - -func TestNew(t *testing.T) { - input := ` - @url = jumpscale.digitalme.package - name = "UNKNOWN" (S) #official name of the package, there can be no overlap (can be dot notation) - enable = true (B) - args = (LO) !jumpscale.digitalme.package.arg - loaders= (LO) !jumpscale.digitalme.package.loader - - @url = jumpscale.digitalme.package.arg - key = "" (S) - val = "" (S) - - @url = jumpscale.digitalme.package.loader - giturl = (S) - dest = (S) - enable = true (B) - ` - - schema, err := New(strings.NewReader(input)) - - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Len(t, schema, 3); !ok { - t.Fatal() - } - - if ok := assert.Equal(t, &Object{ - URL: "jumpscale.digitalme.package", - IsRoot: true, - Properties: []Property{ - {Name: "name", Type: Type{ - Default: `"UNKNOWN"`, - Kind: StringKind, - }}, - {Name: "enable", Type: Type{ - Default: `true`, - Kind: BoolKind, - }}, - {Name: "args", Type: Type{ - Kind: ListKind, - Element: &Type{ - Kind: ObjectKind, - Reference: "jumpscale.digitalme.package.arg", - }, - }}, - {Name: "loaders", Type: Type{ - Kind: ListKind, - Element: &Type{ - Kind: ObjectKind, - Reference: "jumpscale.digitalme.package.loader", - }, - }}, - }, - }, schema[0]); !ok { - t.Error() - } - - if ok := assert.Equal(t, &Object{ - URL: "jumpscale.digitalme.package.arg", - Properties: []Property{ - {Name: "key", Type: Type{ - Default: `""`, - Kind: StringKind, - }}, - {Name: "val", Type: Type{ - Default: `""`, - Kind: StringKind, - }}, - }, - }, schema[1]); !ok { - t.Error() - } - - if ok := assert.Equal(t, &Object{ - URL: "jumpscale.digitalme.package.loader", - Properties: []Property{ - {Name: "giturl", Type: Type{ - Kind: StringKind, - }}, - {Name: "dest", Type: Type{ - Kind: StringKind, - }}, - {Name: "enable", Type: Type{ - Default: "true", - Kind: BoolKind, - }}, - }, - }, schema[2]); !ok { - t.Error() - } -} - -func TestObjectDefault(t *testing.T) { - const input = ` -@url = jumpscale.digitalme.package -name = "UNKNOWN" (S) #official name of the package, there can be no overlap (can be dot notation) -enable = true (B) -unset = (S) -args = "1,2,3" (LI) -int = 1 (I) -strings = ["hello", "world"] (LS) -` - - schema, err := New(strings.NewReader(input)) - - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Len(t, schema, 1); !ok { - t.Fatal() - } - - obj := schema[0] - - type T struct { - Name string `json:"name"` - Enable bool `json:"enable"` - Args []int64 `json:"args"` - Int int64 `json:"int"` - Strings []string `json:"strings"` - } - - var data T - if err := json.Unmarshal([]byte(obj.Default()), &data); err != nil { - t.Fatal(err) - } - - if ok := assert.Equal(t, T{ - Name: "UNKNOWN", - Enable: true, - Args: []int64{1, 2, 3}, - Int: 1, - Strings: []string{"hello", "world"}, - }, data); !ok { - t.Error() - } -} diff --git a/pkg/schema/types.go b/pkg/schema/types.go deleted file mode 100644 index 1f2cec198..000000000 --- a/pkg/schema/types.go +++ /dev/null @@ -1,256 +0,0 @@ -package schema - -import ( - "encoding/json" - "fmt" - "net" - "regexp" - "strconv" - "time" - - "math/big" -) - -var ( - dateRe = regexp.MustCompile(`^(?:(\d{2,4})/)?(\d{2})/(\d{2,4})(?:\s+(\d{1,2})(am|pm)?:(\d{1,2}))?$`) -) - -// Numeric type. this type is tricky so we just going to handle it as string -// for now. -type Numeric string - -// Float64 returns parsed float64 value -func (n Numeric) Float64() (float64, error) { - return strconv.ParseFloat(string(n), 64) -} - -// BigInt returns parsed big.Int value -func (n Numeric) BigInt() (*big.Int, error) { - bi := new(big.Int) - _, ok := bi.SetString(string(n), 10) - if !ok { - return nil, fmt.Errorf("failed to return '%s' as a big Int", n) - } - return bi, nil -} - -// UnmarshalJSON method -func (n *Numeric) UnmarshalJSON(in []byte) error { - var v interface{} - if err := json.Unmarshal(in, &v); err != nil { - return err - } - - *n = Numeric(fmt.Sprint(v)) - - return nil -} - -// Date a jumpscale date wrapper -type Date struct{ time.Time } - -// UnmarshalJSON method -func (d *Date) UnmarshalJSON(bytes []byte) error { - var inI interface{} - if err := json.Unmarshal(bytes, &inI); err != nil { - return err - } - - var in string - switch v := inI.(type) { - case int64: - d.Time = time.Unix(v, 0).UTC() - return nil - case float64: - d.Time = time.Unix(int64(v), 0).UTC() - return nil - case string: - in = v - default: - return fmt.Errorf("unknown date format: %T(%s)", v, string(bytes)) - } - - if len(in) == 0 { - //null date - d.Time = time.Time{} - return nil - } - - m := dateRe.FindStringSubmatch(in) - if m == nil { - return fmt.Errorf("invalid date string '%s'", in) - } - - first := m[1] - month := m[2] - last := m[3] - - hour := m[4] - ampm := m[5] - min := m[6] - - var year string - var day string - - if first == "" { - year = fmt.Sprint(time.Now().Year()) - day = last - } else if len(first) == 4 && len(last) == 4 { - return fmt.Errorf("invalid date format ambiguous year: %s", in) - } else if len(last) == 4 { - year = last - day = first - } else { - // both ar 2 or first is 4 and last is 2 - year = first - day = last - } - - if hour == "" { - hour = "0" - } - if min == "" { - min = "0" - } - - var values []int - for _, str := range []string{year, month, day, hour, min} { - value, err := strconv.Atoi(str) - if err != nil { - return fmt.Errorf("invalid integer value '%s' in date", str) - } - values = append(values, value) - } - - if values[0] < 100 { - values[0] += 2000 - } - - if ampm == "pm" { - values[3] += 12 - } - - d.Time = time.Date(values[0], time.Month(values[1]), values[2], values[3], values[4], 0, 0, time.UTC) - - return nil -} - -// MarshalJSON formats a text -func (d Date) MarshalJSON() ([]byte, error) { - if d.Time.IsZero() { - return []byte(`0`), nil - } - return []byte(fmt.Sprintf(`%d`, d.Unix())), nil -} - -// String implements stringer interface -func (d Date) String() ([]byte, error) { - if d.Time.IsZero() { - return []byte(`""`), nil - } - return []byte(fmt.Sprintf(`"%s"`, d.Format("02/01/2006 15:04"))), nil -} - -// IPRange type -type IPRange struct{ net.IPNet } - -// ParseIPRange parse iprange -func ParseIPRange(txt string) (r IPRange, err error) { - if len(txt) == 0 { - //empty ip net value - return r, nil - } - //fmt.Println("parsing: ", string(text)) - ip, net, err := net.ParseCIDR(txt) - if err != nil { - return r, err - } - - net.IP = ip - r.IPNet = *net - return -} - -// MustParseIPRange prases iprange, panics if invalid -func MustParseIPRange(txt string) IPRange { - r, err := ParseIPRange(txt) - if err != nil { - panic(err) - } - return r -} - -// UnmarshalText loads IPRange from string -func (i *IPRange) UnmarshalText(text []byte) error { - v, err := ParseIPRange(string(text)) - if err != nil { - return err - } - - i.IPNet = v.IPNet - return nil -} - -// MarshalJSON dumps iprange as a string -func (i IPRange) MarshalJSON() ([]byte, error) { - if len(i.IPNet.IP) == 0 { - return []byte(`""`), nil - } - v := fmt.Sprint("\"", i.String(), "\"") - return []byte(v), nil -} - -func (i IPRange) String() string { - return i.IPNet.String() -} - -// Email type -type Email string - -var ( - emailP = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") -) - -// UnmarshalText loads IPRange from string -func (e *Email) UnmarshalText(text []byte) error { - v := string(text) - if v == "" { - return nil - } - - if !emailP.MatchString(v) { - return fmt.Errorf("invalid email address: %s", v) - } - - *e = Email(v) - return nil -} - -// ID object id -type ID int64 - -// MacAddress type -type MacAddress struct{ net.HardwareAddr } - -// MarshalText marshals MacAddress type to a string -func (mac MacAddress) MarshalText() ([]byte, error) { - if mac.HardwareAddr == nil { - return nil, nil - } else if mac.HardwareAddr.String() == "" { - return nil, nil - } - return []byte(mac.HardwareAddr.String()), nil -} - -// UnmarshalText loads a macaddress from a string -func (mac *MacAddress) UnmarshalText(addr []byte) error { - if len(addr) == 0 { - return nil - } - addr, err := net.ParseMAC(string(addr)) - if err != nil { - return err - } - mac.HardwareAddr = addr - return nil -} diff --git a/pkg/schema/types_test.go b/pkg/schema/types_test.go deleted file mode 100644 index 1c78152b4..000000000 --- a/pkg/schema/types_test.go +++ /dev/null @@ -1,265 +0,0 @@ -package schema - -import ( - "encoding/json" - "fmt" - "net" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/stretchr/testify/assert" -) - -func TestParseDate(t *testing.T) { - year := time.Now().Year() - cases := []struct { - Input string - Output time.Time - }{ - {`"01/02/03"`, time.Date(2001, time.Month(2), 3, 0, 0, 0, 0, time.UTC)}, - {`"01/01/2019 9pm:10"`, time.Date(2019, time.Month(1), 1, 21, 10, 0, 0, time.UTC)}, - {`"15/12/03 22:00"`, time.Date(2015, time.Month(12), 3, 22, 0, 0, 0, time.UTC)}, - {`"06/28"`, time.Date(year, time.Month(6), 28, 0, 0, 0, 0, time.UTC)}, - } - - for _, c := range cases { - t.Run(c.Input, func(t *testing.T) { - var d Date - err := json.Unmarshal([]byte(c.Input), &d) - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Equal(t, c.Output, d.Time); !ok { - t.Error() - } - }) - } -} - -func TestParseEmail(t *testing.T) { - cases := []struct { - Input string - Output Email - }{ - {`"azmy@gmail.com"`, Email("azmy@gmail.com")}, - {`"a@b"`, Email("a@b")}, - {`""`, Email("")}, - } - - for _, c := range cases { - t.Run(c.Input, func(t *testing.T) { - var d Email - err := json.Unmarshal([]byte(c.Input), &d) - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Equal(t, c.Output, d); !ok { - t.Error() - } - }) - } - - failed := []string{ - `"gmail.com"`, - `"a"`, - } - - for _, c := range failed { - t.Run(c, func(t *testing.T) { - var d Email - err := json.Unmarshal([]byte(c), &d) - if ok := assert.Error(t, err); !ok { - t.Fatal() - } - - }) - } - -} - -func TestNumericBigInt(t *testing.T) { - const inputValue = "1344719667586153181419716641724567886890850696275767987106294472017884974410332069524504824747437757" - var n Numeric - err := n.UnmarshalJSON([]byte(fmt.Sprintf(`"%s"`, inputValue))) - if err != nil { - t.Fatal(err) - } - bi, err := n.BigInt() - if err != nil { - t.Fatal(err) - } - output := bi.String() - if inputValue != output { - t.Fatalf("%s != %s", inputValue, output) - } -} - -func TestDateNil(t *testing.T) { - var d Date - - b, err := json.Marshal(d) - require.NoError(t, err) - - assert.Equal(t, []byte(`0`), b) - - d = Date{Time: time.Unix(500, 0)} - b, err = json.Marshal(d) - require.NoError(t, err) - - assert.Equal(t, []byte(`500`), b) -} - -func TestParseIPRange(t *testing.T) { - parser := func(t *testing.T, in string) IPRange { - //note in is surrounded by "" because it's json - var str string - if err := json.Unmarshal([]byte(in), &str); err != nil { - t.Fatal(err) - } - - if len(str) == 0 { - return IPRange{} - } - - ip, ipNet, err := net.ParseCIDR(str) - if err != nil { - t.Fatal(err) - } - ipNet.IP = ip - return IPRange{*ipNet} - } - - cases := []struct { - Input string - Output func(*testing.T, string) IPRange - }{ - {`"192.168.1.0/24"`, parser}, - {`"2001:db8::/32"`, parser}, - {`""`, parser}, - } - - for _, c := range cases { - t.Run(c.Input, func(t *testing.T) { - var d IPRange - err := json.Unmarshal([]byte(c.Input), &d) - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Equal(t, c.Output(t, c.Input), d); !ok { - t.Error() - } - }) - } -} - -func TestDumpIPRange(t *testing.T) { - mustParse := func(in string) IPRange { - _, ipNet, err := net.ParseCIDR(in) - if err != nil { - panic(err) - } - return IPRange{*ipNet} - } - - cases := []struct { - Input IPRange - Output string - }{ - {IPRange{}, `""`}, - {mustParse("192.168.1.0/24"), `"192.168.1.0/24"`}, - {mustParse("2001:db8::/32"), `"2001:db8::/32"`}, - } - - for _, c := range cases { - t.Run(c.Output, func(t *testing.T) { - out, err := json.Marshal(c.Input) - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Equal(t, c.Output, string(out)); !ok { - t.Error() - } - }) - } -} - -func TestParseMacAddress(t *testing.T) { - parser := func(t *testing.T, in string) MacAddress { - //note in is surrounded by "" because it's json - var str string - if err := json.Unmarshal([]byte(in), &str); err != nil { - t.Fatal(err) - } - - if len(str) == 0 { - return MacAddress{} - } - - mac, err := net.ParseMAC(str) - if err != nil { - t.Fatal(err) - } - return MacAddress{mac} - } - - cases := []struct { - Input string - Output func(*testing.T, string) MacAddress - }{ - {`"54:45:46:f6:02:61"`, parser}, - {`"FF:FF:FF:FF:FF:FF"`, parser}, - {`""`, parser}, - } - - for _, c := range cases { - t.Run(c.Input, func(t *testing.T) { - var d MacAddress - err := json.Unmarshal([]byte(c.Input), &d) - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Equal(t, c.Output(t, c.Input), d); !ok { - t.Error() - } - }) - } -} - -func TestDumpMacaddress(t *testing.T) { - mustParse := func(in string) MacAddress { - mac, err := net.ParseMAC(in) - if err != nil { - require.NoError(t, err) - } - return MacAddress{mac} - } - - cases := []struct { - Input MacAddress - Output string - }{ - {MacAddress{}, `""`}, - {mustParse("54:45:46:f6:02:61"), `"54:45:46:f6:02:61"`}, - {mustParse("FF:FF:FF:FF:FF:FF"), `"ff:ff:ff:ff:ff:ff"`}, - } - - for _, c := range cases { - t.Run(c.Output, func(t *testing.T) { - out, err := json.Marshal(c.Input) - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - if ok := assert.Equal(t, c.Output, string(out)); !ok { - t.Error() - } - }) - } -} diff --git a/tools/client/filter.go b/tools/client/filter.go deleted file mode 100644 index 4f5d8ed8b..000000000 --- a/tools/client/filter.go +++ /dev/null @@ -1,96 +0,0 @@ -package client - -import ( - "fmt" - "net/url" -) - -// NodeFilter used to build a query for node list -type NodeFilter struct { - farm *int64 - country *string - city *string - cru *int64 - mru *int64 - sru *int64 - hru *int64 - proofs *bool -} - -// WithFarm filter with farm -func (n NodeFilter) WithFarm(id int64) NodeFilter { - n.farm = &id - return n -} - -//WithCountry filter with country -func (n NodeFilter) WithCountry(country string) NodeFilter { - n.country = &country - return n -} - -//WithCity filter with city -func (n NodeFilter) WithCity(city string) NodeFilter { - n.city = &city - return n -} - -//WithCRU filter with CRU -func (n NodeFilter) WithCRU(cru int64) NodeFilter { - n.cru = &cru - return n -} - -// WithMRU filter with mru -func (n NodeFilter) WithMRU(sru int64) NodeFilter { - n.sru = &sru - return n -} - -// WithHRU filter with HRU -func (n NodeFilter) WithHRU(hru int64) NodeFilter { - n.hru = &hru - return n -} - -// WithProofs filter with proofs -func (n NodeFilter) WithProofs(proofs bool) NodeFilter { - n.proofs = &proofs - return n -} - -// Apply fills query -func (n NodeFilter) Apply(query url.Values) { - - if n.farm != nil { - query.Set("farm", fmt.Sprint(*n.farm)) - } - - if n.country != nil { - query.Set("country", *n.country) - } - - if n.city != nil { - query.Set("city", *n.city) - } - - if n.cru != nil { - query.Set("cru", fmt.Sprint(*n.cru)) - } - - if n.mru != nil { - query.Set("mru", fmt.Sprint(*n.mru)) - } - - if n.sru != nil { - query.Set("sru", fmt.Sprint(*n.sru)) - } - - if n.hru != nil { - query.Set("hru", fmt.Sprint(*n.hru)) - } - - if n.proofs != nil { - query.Set("proofs", fmt.Sprint(*n.proofs)) - } -} diff --git a/tools/client/http.go b/tools/client/http.go deleted file mode 100644 index fc76b2ac7..000000000 --- a/tools/client/http.go +++ /dev/null @@ -1,216 +0,0 @@ -package client - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "path/filepath" - - "github.com/pkg/errors" - "github.com/zaibon/httpsig" -) - -var ( - successCodes = []int{ - http.StatusOK, - http.StatusCreated, - } -) - -type httpClient struct { - u *url.URL - cl http.Client - signer *httpsig.Signer - identity string -} - -// HTTPError is the error type returned by the client -// it contains the error and the HTTP response -type HTTPError struct { - resp *http.Response - err error -} - -func (h HTTPError) Error() string { - return fmt.Sprintf("%v status:%s", h.err, h.resp.Status) -} - -// Response return the HTTP response that trigger this error -func (h HTTPError) Response() http.Response { - return *h.resp -} - -func newHTTPClient(raw string, id Identity) (*httpClient, error) { - u, err := url.Parse(raw) - if err != nil { - return nil, errors.Wrap(err, "invalid url") - } - - var signer *httpsig.Signer - if id != nil { - signer = httpsig.NewSigner(id.Identity(), id.PrivateKey(), httpsig.Ed25519, []string{"(created)", "date", "threebot-id"}) - } - - return &httpClient{ - u: u, - signer: signer, - identity: id.Identity(), - }, nil -} - -func (c *httpClient) url(p ...string) string { - b := *c.u - b.Path = filepath.Join(b.Path, filepath.Join(p...)) - - return b.String() -} - -func (c *httpClient) sign(r *http.Request) error { - if c.signer == nil { - return nil - } - - r.Header.Set(http.CanonicalHeaderKey("threebot-id"), c.identity) - return c.signer.Sign(r) -} - -func (c *httpClient) process(response *http.Response, output interface{}, expect ...int) error { - defer response.Body.Close() - - if len(expect) == 0 { - expect = successCodes - } - - in := func(i int, l []int) bool { - for _, x := range l { - if x == i { - return true - } - } - return false - } - - dec := json.NewDecoder(response.Body) - if !in(response.StatusCode, expect) { - var output struct { - E string `json:"error"` - } - - if err := dec.Decode(&output); err != nil { - return errors.Wrapf(HTTPError{ - err: err, - resp: response, - }, "failed to load error while processing invalid return code of: %s", response.Status) - } - - return HTTPError{ - err: fmt.Errorf(output.E), - resp: response, - } - } - - if output == nil { - //discard output - ioutil.ReadAll(response.Body) - return nil - } - - if err := dec.Decode(output); err != nil { - return HTTPError{ - err: errors.Wrap(err, "failed to load output"), - resp: response, - } - } - - return nil -} - -func (c *httpClient) post(u string, input interface{}, output interface{}, expect ...int) error { - var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(input); err != nil { - return errors.Wrap(err, "failed to serialize request body") - } - - req, err := http.NewRequest(http.MethodPost, u, &buf) - if err != nil { - return errors.Wrap(err, "failed to create new HTTP request") - } - - if err := c.sign(req); err != nil { - return errors.Wrap(err, "failed to sign HTTP request") - } - response, err := c.cl.Do(req) - if err != nil { - return errors.Wrap(err, "failed to send request") - } - - return c.process(response, output, expect...) -} - -func (c *httpClient) put(u string, input interface{}, output interface{}, expect ...int) error { - var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(input); err != nil { - return errors.Wrap(err, "failed to serialize request body") - } - req, err := http.NewRequest(http.MethodPut, u, &buf) - if err != nil { - return errors.Wrap(err, "failed to build request") - } - - if err := c.sign(req); err != nil { - return errors.Wrap(err, "failed to sign HTTP request") - } - - response, err := c.cl.Do(req) - if err != nil { - return errors.Wrap(err, "failed to send request") - } - - return c.process(response, output, expect...) -} - -func (c *httpClient) get(u string, query url.Values, output interface{}, expect ...int) error { - if len(query) > 0 { - u = fmt.Sprintf("%s?%s", u, query.Encode()) - } - - req, err := http.NewRequest(http.MethodGet, u, nil) - if err != nil { - return errors.Wrap(err, "failed to create new HTTP request") - } - - if err := c.sign(req); err != nil { - return errors.Wrap(err, "failed to sign HTTP request") - } - - response, err := c.cl.Do(req) - if err != nil { - return errors.Wrap(err, "failed to send request") - } - - return c.process(response, output, expect...) -} - -func (c *httpClient) delete(u string, query url.Values, output interface{}, expect ...int) error { - if len(query) > 0 { - u = fmt.Sprintf("%s?%s", u, query.Encode()) - } - req, err := http.NewRequest(http.MethodDelete, u, nil) - if err != nil { - return errors.Wrap(err, "failed to build request") - } - - if err := c.sign(req); err != nil { - return errors.Wrap(err, "failed to sign HTTP request") - } - - response, err := c.cl.Do(req) - if err != nil { - return errors.Wrap(err, "failed to send request") - } - - return c.process(response, output, expect...) -} diff --git a/tools/client/phonebook.go b/tools/client/phonebook.go deleted file mode 100644 index bcddf8b48..000000000 --- a/tools/client/phonebook.go +++ /dev/null @@ -1,64 +0,0 @@ -package client - -import ( - "fmt" - "net/http" - "net/url" - - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models/generated/phonebook" -) - -type httpPhonebook struct { - *httpClient -} - -func (p *httpPhonebook) Create(user phonebook.User) (schema.ID, error) { - var out phonebook.User - if err := p.post(p.url("users"), user, &out); err != nil { - return 0, err - } - - return out.ID, nil -} - -func (p *httpPhonebook) List(name, email string, page *Pager) (output []phonebook.User, err error) { - query := url.Values{} - page.apply(query) - if len(name) != 0 { - query.Set("name", name) - } - if len(email) != 0 { - query.Set("email", email) - } - - err = p.get(p.url("users"), query, &output, http.StatusOK) - - return -} - -func (p *httpPhonebook) Get(id schema.ID) (user phonebook.User, err error) { - err = p.get(p.url("users", fmt.Sprint(id)), nil, &user, http.StatusOK) - return -} - -// Validate the signature of this message for the user, signature and message are hex encoded -func (p *httpPhonebook) Validate(id schema.ID, message, signature string) (bool, error) { - var input struct { - S string `json:"signature"` - M string `json:"payload"` - } - input.S = signature - input.M = message - - var output struct { - V bool `json:"is_valid"` - } - - err := p.post(p.url("users", fmt.Sprint(id), "validate"), input, &output, http.StatusOK) - if err != nil { - return false, err - } - - return output.V, nil -} diff --git a/tools/client/signer.go b/tools/client/signer.go deleted file mode 100644 index 9dde1facb..000000000 --- a/tools/client/signer.go +++ /dev/null @@ -1,91 +0,0 @@ -package client - -import ( - "bytes" - "encoding/hex" - "encoding/json" - "fmt" - - "github.com/threefoldtech/zos/pkg/crypto" - "github.com/threefoldtech/zos/pkg/identity" - "github.com/threefoldtech/zos/pkg/schema" -) - -// Signer is a utility to easily sign payloads -type Signer struct { - pair identity.KeyPair -} - -// NewSigner create a signer with a seed -func NewSigner(seed []byte) (*Signer, error) { - pair, err := identity.FromSeed(seed) - if err != nil { - return nil, err - } - - return &Signer{pair: pair}, nil -} - -// NewSignerFromFile loads signer from a seed file -func NewSignerFromFile(path string) (*Signer, error) { - pair, err := identity.LoadKeyPair(path) - - if err != nil { - return nil, err - } - - return &Signer{pair: pair}, nil -} - -// SignHex like sign, but return message and signature in hex encoded format -func (s *Signer) SignHex(o ...interface{}) (string, string, error) { - msg, sig, err := s.Sign(o...) - if err != nil { - return "", "", err - } - - return hex.EncodeToString(msg), hex.EncodeToString(sig), nil -} - -// Sign constructs a message from all it's arguments then sign it -func (s *Signer) Sign(o ...interface{}) ([]byte, []byte, error) { - var buf bytes.Buffer - for _, x := range o { - switch x := x.(type) { - case nil: - case string: - buf.WriteString(x) - case fmt.Stringer: - buf.WriteString(x.String()) - case []byte: - buf.Write(x) - case json.RawMessage: - buf.Write(x) - case byte: - buf.WriteString(fmt.Sprint(x)) - // all int types - case schema.ID: - buf.WriteString(fmt.Sprint(x)) - case int: - buf.WriteString(fmt.Sprint(x)) - case int8: - buf.WriteString(fmt.Sprint(x)) - case int16: - buf.WriteString(fmt.Sprint(x)) - case int32: - buf.WriteString(fmt.Sprint(x)) - case int64: - buf.WriteString(fmt.Sprint(x)) - // all float types - case float32: - buf.WriteString(fmt.Sprint(x)) - case float64: - buf.WriteString(fmt.Sprint(x)) - default: - return nil, nil, fmt.Errorf("unsupported type") - } - } - msg := buf.Bytes() - sig, err := crypto.Sign(s.pair.PrivateKey, msg) - return msg, sig, err -} diff --git a/tools/client/workloads.go b/tools/client/workloads.go deleted file mode 100644 index 20f002185..000000000 --- a/tools/client/workloads.go +++ /dev/null @@ -1,153 +0,0 @@ -package client - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" - wrklds "github.com/threefoldtech/zos/tools/explorer/pkg/workloads" -) - -type httpWorkloads struct { - *httpClient -} - -func (w *httpWorkloads) Create(reservation workloads.Reservation) (resp wrklds.ReservationCreateResponse, err error) { - err = w.post(w.url("reservations"), reservation, &resp, http.StatusCreated) - return -} - -func (w *httpWorkloads) List(nextAction *workloads.NextActionEnum, customerTid int64, page *Pager) (reservation []workloads.Reservation, err error) { - query := url.Values{} - if nextAction != nil { - query.Set("next_action", fmt.Sprintf("%d", nextAction)) - } - if customerTid != 0 { - query.Set("customer_tid", fmt.Sprint(customerTid)) - } - page.apply(query) - - err = w.get(w.url("reservations"), query, &reservation, http.StatusOK) - return -} - -func (w *httpWorkloads) Get(id schema.ID) (reservation workloads.Reservation, err error) { - err = w.get(w.url("reservations", fmt.Sprint(id)), nil, &reservation, http.StatusOK) - return -} - -func (w *httpWorkloads) SignProvision(id schema.ID, user schema.ID, signature string) error { - return w.post( - w.url("reservations", fmt.Sprint(id), "sign", "provision"), - workloads.SigningSignature{ - Tid: int64(user), - Signature: signature, - }, - nil, - http.StatusCreated, - ) -} - -func (w *httpWorkloads) SignDelete(id schema.ID, user schema.ID, signature string) error { - return w.post( - w.url("reservations", fmt.Sprint(id), "sign", "delete"), - workloads.SigningSignature{ - Tid: int64(user), - Signature: signature, - }, - nil, - http.StatusCreated, - ) -} - -type intermediateWL struct { - workloads.ReservationWorkload - Content json.RawMessage `json:"content"` -} - -func (wl *intermediateWL) Workload() (result workloads.ReservationWorkload, err error) { - result = wl.ReservationWorkload - switch wl.Type { - case workloads.WorkloadTypeContainer: - var o workloads.Container - if err := json.Unmarshal(wl.Content, &o); err != nil { - return result, err - } - result.Content = o - case workloads.WorkloadTypeKubernetes: - var o workloads.K8S - if err := json.Unmarshal(wl.Content, &o); err != nil { - return result, err - } - result.Content = o - case workloads.WorkloadTypeNetwork: - var o workloads.Network - if err := json.Unmarshal(wl.Content, &o); err != nil { - return result, err - } - result.Content = o - case workloads.WorkloadTypeVolume: - var o workloads.Volume - if err := json.Unmarshal(wl.Content, &o); err != nil { - return result, err - } - result.Content = o - case workloads.WorkloadTypeZDB: - var o workloads.ZDB - if err := json.Unmarshal(wl.Content, &o); err != nil { - return result, err - } - result.Content = o - default: - return result, fmt.Errorf("unknown workload type") - } - - return -} - -func (w *httpWorkloads) Workloads(nodeID string, from uint64) ([]workloads.ReservationWorkload, error) { - query := url.Values{} - query.Set("from", fmt.Sprint(from)) - - var list []intermediateWL - - err := w.get( - w.url("reservations", "workloads", nodeID), - query, - &list, - http.StatusOK, - ) - if err != nil { - return nil, err - } - results := make([]workloads.ReservationWorkload, 0, len(list)) - for _, i := range list { - wl, err := i.Workload() - if err != nil { - return nil, err - } - results = append(results, wl) - } - return results, err -} - -func (w *httpWorkloads) WorkloadGet(gwid string) (result workloads.ReservationWorkload, err error) { - var output intermediateWL - err = w.get(w.url("reservations", "workloads", gwid), nil, &output, http.StatusOK) - if err != nil { - return - } - - return output.Workload() -} - -func (w *httpWorkloads) WorkloadPutResult(nodeID, gwid string, result workloads.Result) error { - return w.put(w.url("reservations", "workloads", gwid, nodeID), result, nil, http.StatusCreated) -} - -func (w *httpWorkloads) WorkloadPutDeleted(nodeID, gwid string) error { - return w.delete(w.url("reservations", "workloads", gwid, nodeID), nil, nil, http.StatusOK) -} diff --git a/tools/explorer/.gitignore b/tools/explorer/.gitignore deleted file mode 100644 index 917dd5223..000000000 --- a/tools/explorer/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# binary -explorer - -# generated code -statik \ No newline at end of file diff --git a/tools/explorer/config/global.go b/tools/explorer/config/global.go deleted file mode 100644 index c44c8b176..000000000 --- a/tools/explorer/config/global.go +++ /dev/null @@ -1,37 +0,0 @@ -package config - -import ( - "fmt" - "strings" - - "github.com/threefoldtech/zos/tools/explorer/pkg/stellar" -) - -// Settings struct -type Settings struct { - Network string -} - -var ( - // Config is global explorer config - Config Settings - - possibleNetworks = []string{stellar.NetworkProduction, stellar.NetworkTest} -) - -// Valid checks if Config is filled with valid data -func Valid() error { - in := func(s string, l []string) bool { - for _, a := range l { - if strings.EqualFold(s, a) { - return true - } - } - return false - } - if Config.Network != "" && !in(Config.Network, possibleNetworks) { - return fmt.Errorf("invalid network '%s'", Config.Network) - } - - return nil -} diff --git a/tools/explorer/frontend/.gitignore b/tools/explorer/frontend/.gitignore deleted file mode 100644 index f34b5f729..000000000 --- a/tools/explorer/frontend/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -.DS_Store -node_modules -/dist - -# local env files -.env.local -.env.*.local - -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Editor directories and files -.idea -.vscode -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# Lock files -package-lock.json -yarn.lock \ No newline at end of file diff --git a/tools/explorer/frontend/babel.config.js b/tools/explorer/frontend/babel.config.js deleted file mode 100644 index e9558405f..000000000 --- a/tools/explorer/frontend/babel.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - presets: [ - '@vue/cli-plugin-babel/preset' - ] -} diff --git a/tools/explorer/frontend/package.json b/tools/explorer/frontend/package.json deleted file mode 100644 index ac6b6acd7..000000000 --- a/tools/explorer/frontend/package.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "tf-cockpit", - "version": "0.1.0", - "private": true, - "scripts": { - "serve": "vue-cli-service serve", - "build": "vue-cli-service build", - "lint": "vue-cli-service lint" - }, - "dependencies": { - "axios": "^0.19.2", - "chart.js": "^2.9.3", - "core-js": "^3.6.4", - "lodash": "^4.17.15", - "moment": "^2.24.0", - "moment-duration-format": "^2.3.2", - "vue": "^2.6.11", - "vue-chartjs": "^3.5.0", - "vue-chartkick": "^0.5.3", - "vue-google-charts": "^0.3.2", - "vue-router": "^3.1.6", - "vue2-google-maps": "^0.10.7", - "vuetify": "^2.2.20", - "vuex": "^3.1.3" - }, - "devDependencies": { - "@fortawesome/fontawesome-free": "^5.13.0", - "@vue/cli-plugin-babel": "~4.2.3", - "@vue/cli-plugin-eslint": "~4.2.3", - "@vue/cli-service": "~4.2.3", - "@vue/eslint-config-standard": "^4.0.0", - "babel-eslint": "^10.1.0", - "eslint": "^5.16.0", - "eslint-plugin-vue": "^5.0.0", - "node-sass": "^4.13.1", - "sass": "^1.26.3", - "sass-loader": "^7.1.0", - "vue-cli-plugin-vuetify": "~2.0.5", - "vue-template-compiler": "^2.6.11", - "vuetify-loader": "^1.4.3" - }, - "eslintConfig": { - "root": true, - "env": { - "node": true - }, - "extends": [ - "plugin:vue/essential", - "@vue/standard" - ], - "rules": {}, - "parserOptions": { - "parser": "babel-eslint" - } - }, - "postcss": { - "plugins": { - "autoprefixer": {} - } - }, - "browserslist": [ - "> 1%", - "last 2 versions" - ] -} diff --git a/tools/explorer/frontend/public/config.js b/tools/explorer/frontend/public/config.js deleted file mode 100644 index ffbcf4f40..000000000 --- a/tools/explorer/frontend/public/config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default ({ - tfApiUrl: '/explorer', - tfExplorerUrl: 'https://explorer.threefoldtoken.com/explorer' - -}) diff --git a/tools/explorer/frontend/public/favicon.ico b/tools/explorer/frontend/public/favicon.ico deleted file mode 100644 index 21f8c25d29a544153e4e682e9ed385bc2dd9d143..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeI3O-vI(6vwBaK#AeQMv5nU(8QaZ42i~YB#1Fl=utgr;uSp_jR!DZj3*^V;|UEH zBjQ0NVleR|6#O9Z1A}@tek&RfB=z@T2TRvVX=#TEv&pZoJKeYMzi)PDXJ@BM1yrG` zsZrR7>fE5zW~Edl;;mOG^_H>+MYQ)opQYSD_ZY-2Wz^|6T6Zr0X0IKWf3x|2 zXL5=pblo|X)%{8dEx!u zHmJ)ZTZpw5rqyFV!Z-MuCq5HL-f!*!nfo;XdhSj>?HA(iH5gxx^@-v5KMcp0Ia;Rb zU-G{c;-5pu1w*taw?}T>p8>`qgHVS|FF%{xocYDaiEs{y~=h z@{Bp@?L%}d6!}k1GerL~5RJtKo71EzIiE*+YKxBb)K;cTvOj%+dXV`YFVbM>AO2DN zrXW?{Jh6Nt2mTGj9)UL?eXCT+@59OQ+XnWRKsa5v-Wp-YEQf-Pw^~ZJr%3~T(nd{! xw3iD(e575I_Ab-=yzCCSKDl+0R!bPCViM8ibLxwh?GI;{DM|nU diff --git a/tools/explorer/frontend/public/index.html b/tools/explorer/frontend/public/index.html deleted file mode 100644 index 976d4a2a3..000000000 --- a/tools/explorer/frontend/public/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - TF Cockpit - - - - - - - -
- - - diff --git a/tools/explorer/frontend/readme.md b/tools/explorer/frontend/readme.md deleted file mode 100644 index 2f9850c74..000000000 --- a/tools/explorer/frontend/readme.md +++ /dev/null @@ -1,46 +0,0 @@ -# TF Cockpit [![Actions Status](https://github.com/threefoldtech/tf_cockpit/workflows/Build/badge.svg)](https://github.com/threefoldtech/tf_cockpit/actions) -TF Cockpit is the main tool for personal management of everything to do with ThreeFold Grid - -Goal is to have a user friendly UI which can be used on desktop and mobile - -There will be 2 main sections: - -- Informational (non password protected) - - TF Grid Capacity & Farmers Overview - - TF Tokens Overview - -- Hands on (password protected) - - Trade: Integrated Marketplace TFT Sales (not in version 1) - - Operate: Farmers - - Use: The Grid - - Support - -More details can be found in the [TF Cockpit Mindmap](https://app.mindmup.com/map/_v2/9cde45609dd811e985b0f137139fe83f) - -New ThreeFold Tech web framework for [TF Cockpit creation](https://app.mindmup.com/map/_v2/d2bdbc709d8211e99a571f71f908147d) - -Owner: @zaibon @jdelrue - -## Specifications - -you can find more detail information and specification in the [specs directory](specs) - -## Frontend getting started - -### Project setup - -``` -yarn install -``` - -#### Compiles and hot-reloads for development - -``` -yarn serve -``` - -### Compiles and minifies for production - -``` -yarn build -``` diff --git a/tools/explorer/frontend/src/App.vue b/tools/explorer/frontend/src/App.vue deleted file mode 100644 index cb75147ae..000000000 --- a/tools/explorer/frontend/src/App.vue +++ /dev/null @@ -1,144 +0,0 @@ - - - - - diff --git a/tools/explorer/frontend/src/assets/logo.jpg b/tools/explorer/frontend/src/assets/logo.jpg deleted file mode 100644 index 84e3e18979b0daa499b8842fe17d9f950fb2ef9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4834 zcmd5Z+x5QM^zRXOMq%0{Zx2b94B2tDfqA4XQZ6bwS zS}ZBDlx#x_8cR{!3N4bP%)Do2R4(rQeBRIdkMle8obP$Q&vWLS=lss{6FwHU0=%QG zgDpT101)^9;Y(DG!=gniX$&`82j|6N0>usXV?|($0APhghBNG}iA%kf5v5-OS)c@P z&`ZYGFM@4Fqd9|rOdlVLS_hy(U)0t=n*E%Z;J+rq4|Wg->Hqk#!y_TKgxGaMBwK{J z5G(ry`uanh5Am#U*g=Tzi}b4{xLt(TO7JHU4rekJ0f3T&n7CGgXNmA?2^Nio=oc8y zf;PbrlUc!0un*!S8j0MR5D#})P80tk00Y96>=*k)S{zC{3C+SxH$pP3 z3Y&sI%iVGe*3+?Ll!6-5= zb#z3?)eRvdxo@^F$y~t34_sOF2*H;LcF#ra%|7Y+v~rooc|*_mVwA>t+@@9?rn^b& zlCFP=fQ7*BKF_Sfv}U_SAQV(9qmO>nn}MbZA zR_&&I46KP&ybg7A^QiexN|_-`tHqtz$@$7Fxb#g{#RVPcP8ma8-AZEKhqyiUvW5-= zm@zk(PF1ppn-bQ-S-H>1{d(+^sRZ?E6p6prZOK*P6eoXUyrB1EPhXiw7tK zg~gyy;?*qbBSslL396(_@J6PV1R|SDGM)opd>mX42p09_K-aRpcxAlD=c(s~6XUbyke>~@6o@!dNDxrExoGB&% zcZYCrhLP>6g0{}=eKU>w|E5f+F4yz8Iry5=)JfMq?m|11JI8t*r%9$riK$&U@NG>@ z+E%JPf8P44xvuuu)&++z^Y~AvRhaz;{&UH>p<$2XjFZ+hPOgl#B1a47h)*HuTD!mb zwk9U`$}(e&yZKE)YY#N*pY`@HuVW=Y(JFTeKJdV;w!T1yr6{S5k{OO=%gPpX+?dt$wof8({LHFYQ?HLiRVRF{9b1OtP{wK6=CXF(3d0 zjY3OdF{ASXw;!|;gP^Q}!QvH7=tPpyY%62y*j^fYn|PMc2tq{!_xKuKQ`lb3OV)cF zPF=gko7|J85i-^1-X3{zT}+hN`v>SW9WdYfYT57IrG3wW+AcQKMoHZx)$%yQ3xX{k zX3alku|dKuQqg`d1af~#B{-f|srKC6dg2D#?=itWh%H+vYaJ5tY=yWv)066RYV_80P>h42#E-R499By*6SoRw}*m$gnf9?q6D}7W64>en$&f9EQ)sh z4?@@Oa;goV>2mZ3LC4oP_{l`Cta$b@WPkJ~#)qE7#hNzR?}y?m-U^Vr^Y9s+^1EZh zm=kvA_;{dR9-pjYdAb@86K70SDgod{kE`61jeOmL*mW*4o-bct>=&en>8XnxRKOi$>c6)Py_Fc2NL3Jgr zE{DQ{8!P9(?)XtHww=SbxXPpA)4CI>*&CWSx%729HB^OUr3G~w@+zj;JGA#kd@PWv zXR~yE5S;mKm{z{)#QYzG%^!6Zf`tov-d0Fm>g`i zH8t1C8ySoVtJ)vmmpCh;bmP9aZ<^wpc9fPMsONh-1+nyxmfnhc+m|R~clh0_ac$n| z`Uj8P?yOFphvywttuanmt?X&%?BS&zxm{e#*`ZM|e>25rb}c7w;K66j>@`k15B8?4 zxJX^p|EzGPuRPxhXN~8OZ2bLi&>y7PlSDO&qt^#HAvjoHoHwClvUSJu3AvF5JMLe^ z3BfS-;+~Qkvq>&jtEzm(N->|4ntwJbeK547#pbZ<bx-_ZRyE+tI z!^o2DCt)Qu6v=FOcG8Z3&Z+q!Nf&Jz<;~4rsGzcnC)Urie>Nj>*9=PKirXdEda2Q7 z0!=rCsApZ(Qff0R&%? zaT4C@1<@$%0gcYymMh*LM5i2nBvWr)OskfC9m9l~kUB$416#DIGUIv5sg8AJE%iys z`Tm7dD7KD7B`Ng=IeDHNDP>k~?%Ev_EOfnI$WA@hvwonU(S3c%Fsw|%-L{$c9eOG*;v?8u0}Ggo=v@0+Ld52~5J*5!SS={f86`%H%+ zCC=5h5~KO5>Iw2S(thYOv5&93UA5k5zHEb95R2-nPA@&GfBo`KlNNfv;5!plbaju?H>-H3@CD<7(u#Y6&kGEFGNea29=x51MK^5)J(KBds(Bm1;At!Uvn8q^4 za*vE_p@otmBgEL*?e@qTa-HZs z+01P+Zudj8D$l*ScA|Xeq43`tc5WH5@RyL^Scm9NqP6deUcaI){>-pZX%w$9rf`_} zXepOIm%k2(&o+`POKJl%tE(9t-SYV|Daqx I+aqlLFRzU=ZvX%Q diff --git a/tools/explorer/frontend/src/assets/logo.png b/tools/explorer/frontend/src/assets/logo.png deleted file mode 100644 index f3d2503fc2a44b5053b0837ebea6e87a2d339a43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6849 zcmaKRcUV(fvo}bjDT-7nLI_nlK}sT_69H+`qzVWDA|yaU?}j417wLi^B1KB1SLsC& zL0ag7$U(XW5YR7p&Ux?sP$d4lvMt8C^+TcQu4F zQqv!UF!I+kw)c0jhd6+g6oCr9P?7)?!qX1ui*iL{p}sKCAGuJ{{W)0z1pLF|=>h}& zt(2Lr0Z`2ig8<5i%Zk}cO5Fm=LByqGWaS`oqChZdEFmc`0hSb#gg|Aap^{+WKOYcj zHjINK)KDG%&s?Mt4CL(T=?;~U@bU2x_mLKN!#GJuK_CzbNw5SMEJorG!}_5;?R>@1 zSl)jns3WlU7^J%=(hUtfmuUCU&C3%8B5C^f5>W2Cy8jW3#{Od{lF1}|?c61##3dzA zsPlFG;l_FzBK}8>|H_Ru_H#!_7$UH4UKo3lKOA}g1(R&|e@}GINYVzX?q=_WLZCgh z)L|eJMce`D0EIwgRaNETDsr+?vQknSGAi=7H00r`QnI%oQnFxm`G2umXso9l+8*&Q z7WqF|$p49js$mdzo^BXpH#gURy=UO;=IMrYc5?@+sR4y_?d*~0^YP7d+y0{}0)zBM zIKVM(DBvICK#~7N0a+PY6)7;u=dutmNqK3AlsrUU9U`d;msiucB_|8|2kY=(7XA;G zwDA8AR)VCA#JOkxm#6oHNS^YVuOU;8p$N)2{`;oF|rQ?B~K$%rHDxXs+_G zF5|-uqHZvSzq}L;5Kcy_P+x0${33}Ofb6+TX&=y;;PkEOpz%+_bCw_{<&~ zeLV|!bP%l1qxywfVr9Z9JI+++EO^x>ZuCK);=$VIG1`kxK8F2M8AdC$iOe3cj1fo(ce4l-9 z7*zKy3={MixvUk=enQE;ED~7tv%qh&3lR<0m??@w{ILF|e#QOyPkFYK!&Up7xWNtL zOW%1QMC<3o;G9_S1;NkPB6bqbCOjeztEc6TsBM<(q9((JKiH{01+Ud=uw9B@{;(JJ z-DxI2*{pMq`q1RQc;V8@gYAY44Z!%#W~M9pRxI(R?SJ7sy7em=Z5DbuDlr@*q|25V)($-f}9c#?D%dU^RS<(wz?{P zFFHtCab*!rl(~j@0(Nadvwg8q|4!}L^>d?0al6}Rrv9$0M#^&@zjbfJy_n!%mVHK4 z6pLRIQ^Uq~dnyy$`ay51Us6WaP%&O;@49m&{G3z7xV3dLtt1VTOMYl3UW~Rm{Eq4m zF?Zl_v;?7EFx1_+#WFUXxcK78IV)FO>42@cm@}2I%pVbZqQ}3;p;sDIm&knay03a^ zn$5}Q$G!@fTwD$e(x-~aWP0h+4NRz$KlnO_H2c< z(XX#lPuW_%H#Q+c&(nRyX1-IadKR-%$4FYC0fsCmL9ky3 zKpxyjd^JFR+vg2!=HWf}2Z?@Td`0EG`kU?{8zKrvtsm)|7>pPk9nu@2^z96aU2<#` z2QhvH5w&V;wER?mopu+nqu*n8p~(%QkwSs&*0eJwa zMXR05`OSFpfyRb!Y_+H@O%Y z0=K^y6B8Gcbl?SA)qMP3Z+=C(?8zL@=74R=EVnE?vY!1BQy2@q*RUgRx4yJ$k}MnL zs!?74QciNb-LcG*&o<9=DSL>1n}ZNd)w1z3-0Pd^4ED1{qd=9|!!N?xnXjM!EuylY z5=!H>&hSofh8V?Jofyd!h`xDI1fYAuV(sZwwN~{$a}MX^=+0TH*SFp$vyxmUv7C*W zv^3Gl0+eTFgBi3FVD;$nhcp)ka*4gSskYIqQ&+M}xP9yLAkWzBI^I%zR^l1e?bW_6 zIn{mo{dD=)9@V?s^fa55jh78rP*Ze<3`tRCN4*mpO$@7a^*2B*7N_|A(Ve2VB|)_o z$=#_=aBkhe(ifX}MLT()@5?OV+~7cXC3r!%{QJxriXo9I%*3q4KT4Xxzyd{ z9;_%=W%q!Vw$Z7F3lUnY+1HZ*lO;4;VR2+i4+D(m#01OYq|L_fbnT;KN<^dkkCwtd zF7n+O7KvAw8c`JUh6LmeIrk4`F3o|AagKSMK3))_5Cv~y2Bb2!Ibg9BO7Vkz?pAYX zoI=B}+$R22&IL`NCYUYjrdhwjnMx_v=-Qcx-jmtN>!Zqf|n1^SWrHy zK|MwJ?Z#^>)rfT5YSY{qjZ&`Fjd;^vv&gF-Yj6$9-Dy$<6zeP4s+78gS2|t%Z309b z0^fp~ue_}i`U9j!<|qF92_3oB09NqgAoehQ`)<)dSfKoJl_A6Ec#*Mx9Cpd-p#$Ez z={AM*r-bQs6*z$!*VA4|QE7bf@-4vb?Q+pPKLkY2{yKsw{&udv_2v8{Dbd zm~8VAv!G~s)`O3|Q6vFUV%8%+?ZSVUa(;fhPNg#vab@J*9XE4#D%)$UU-T5`fwjz! z6&gA^`OGu6aUk{l*h9eB?opVdrHK>Q@U>&JQ_2pR%}TyOXGq_6s56_`U(WoOaAb+K zXQr#6H}>a-GYs9^bGP2Y&hSP5gEtW+GVC4=wy0wQk=~%CSXj=GH6q z-T#s!BV`xZVxm{~jr_ezYRpqqIcXC=Oq`b{lu`Rt(IYr4B91hhVC?yg{ol4WUr3v9 zOAk2LG>CIECZ-WIs0$N}F#eoIUEtZudc7DPYIjzGqDLWk_A4#(LgacooD z2K4IWs@N`Bddm-{%oy}!k0^i6Yh)uJ1S*90>|bm3TOZxcV|ywHUb(+CeX-o1|LTZM zwU>dY3R&U)T(}5#Neh?-CWT~@{6Ke@sI)uSuzoah8COy)w)B)aslJmp`WUcjdia-0 zl2Y}&L~XfA`uYQboAJ1;J{XLhYjH){cObH3FDva+^8ioOQy%Z=xyjGLmWMrzfFoH; zEi3AG`_v+%)&lDJE;iJWJDI@-X9K5O)LD~j*PBe(wu+|%ar~C+LK1+-+lK=t# z+Xc+J7qp~5q=B~rD!x78)?1+KUIbYr^5rcl&tB-cTtj+e%{gpZZ4G~6r15+d|J(ky zjg@@UzMW0k9@S#W(1H{u;Nq(7llJbq;;4t$awM;l&(2s+$l!Ay9^Ge|34CVhr7|BG z?dAR83smef^frq9V(OH+a+ki#q&-7TkWfFM=5bsGbU(8mC;>QTCWL5ydz9s6k@?+V zcjiH`VI=59P-(-DWXZ~5DH>B^_H~;4$)KUhnmGo*G!Tq8^LjfUDO)lASN*=#AY_yS zqW9UX(VOCO&p@kHdUUgsBO0KhXxn1sprK5h8}+>IhX(nSXZKwlNsjk^M|RAaqmCZB zHBolOHYBas@&{PT=R+?d8pZu zUHfyucQ`(umXSW7o?HQ3H21M`ZJal+%*)SH1B1j6rxTlG3hx1IGJN^M7{$j(9V;MZ zRKybgVuxKo#XVM+?*yTy{W+XHaU5Jbt-UG33x{u(N-2wmw;zzPH&4DE103HV@ER86 z|FZEmQb|&1s5#`$4!Cm}&`^{(4V}OP$bk`}v6q6rm;P!H)W|2i^e{7lTk2W@jo_9q z*aw|U7#+g59Fv(5qI`#O-qPj#@_P>PC#I(GSp3DLv7x-dmYK=C7lPF8a)bxb=@)B1 zUZ`EqpXV2dR}B&r`uM}N(TS99ZT0UB%IN|0H%DcVO#T%L_chrgn#m6%x4KE*IMfjX zJ%4veCEqbXZ`H`F_+fELMC@wuy_ch%t*+Z+1I}wN#C+dRrf2X{1C8=yZ_%Pt6wL_~ zZ2NN-hXOT4P4n$QFO7yYHS-4wF1Xfr-meG9Pn;uK51?hfel`d38k{W)F*|gJLT2#T z<~>spMu4(mul-8Q3*pf=N4DcI)zzjqAgbE2eOT7~&f1W3VsdD44Ffe;3mJp-V@8UC z)|qnPc12o~$X-+U@L_lWqv-RtvB~%hLF($%Ew5w>^NR82qC_0FB z)=hP1-OEx?lLi#jnLzH}a;Nvr@JDO-zQWd}#k^an$Kwml;MrD&)sC5b`s0ZkVyPkb zt}-jOq^%_9>YZe7Y}PhW{a)c39G`kg(P4@kxjcYfgB4XOOcmezdUI7j-!gs7oAo2o zx(Ph{G+YZ`a%~kzK!HTAA5NXE-7vOFRr5oqY$rH>WI6SFvWmahFav!CfRMM3%8J&c z*p+%|-fNS_@QrFr(at!JY9jCg9F-%5{nb5Bo~z@Y9m&SHYV`49GAJjA5h~h4(G!Se zZmK{Bo7ivCfvl}@A-ptkFGcWXAzj3xfl{evi-OG(TaCn1FAHxRc{}B|x+Ua1D=I6M z!C^ZIvK6aS_c&(=OQDZfm>O`Nxsw{ta&yiYPA~@e#c%N>>#rq)k6Aru-qD4(D^v)y z*>Rs;YUbD1S8^D(ps6Jbj0K3wJw>L4m)0e(6Pee3Y?gy9i0^bZO?$*sv+xKV?WBlh zAp*;v6w!a8;A7sLB*g-^<$Z4L7|5jXxxP1}hQZ<55f9<^KJ>^mKlWSGaLcO0=$jem zWyZkRwe~u{{tU63DlCaS9$Y4CP4f?+wwa(&1ou)b>72ydrFvm`Rj-0`kBJgK@nd(*Eh!(NC{F-@=FnF&Y!q`7){YsLLHf0_B6aHc# z>WIuHTyJwIH{BJ4)2RtEauC7Yq7Cytc|S)4^*t8Va3HR zg=~sN^tp9re@w=GTx$;zOWMjcg-7X3Wk^N$n;&Kf1RgVG2}2L-(0o)54C509C&77i zrjSi{X*WV=%C17((N^6R4Ya*4#6s_L99RtQ>m(%#nQ#wrRC8Y%yxkH;d!MdY+Tw@r zjpSnK`;C-U{ATcgaxoEpP0Gf+tx);buOMlK=01D|J+ROu37qc*rD(w`#O=3*O*w9?biwNoq3WN1`&Wp8TvKj3C z3HR9ssH7a&Vr<6waJrU zdLg!ieYz%U^bmpn%;(V%%ugMk92&?_XX1K@mwnVSE6!&%P%Wdi7_h`CpScvspMx?N zQUR>oadnG17#hNc$pkTp+9lW+MBKHRZ~74XWUryd)4yd zj98$%XmIL4(9OnoeO5Fnyn&fpQ9b0h4e6EHHw*l68j;>(ya`g^S&y2{O8U>1*>4zR zq*WSI_2o$CHQ?x0!wl9bpx|Cm2+kFMR)oMud1%n2=qn5nE&t@Fgr#=Zv2?}wtEz^T z9rrj=?IH*qI5{G@Rn&}^Z{+TW}mQeb9=8b<_a`&Cm#n%n~ zU47MvCBsdXFB1+adOO)03+nczfWa#vwk#r{o{dF)QWya9v2nv43Zp3%Ps}($lA02*_g25t;|T{A5snSY?3A zrRQ~(Ygh_ebltHo1VCbJb*eOAr;4cnlXLvI>*$-#AVsGg6B1r7@;g^L zFlJ_th0vxO7;-opU@WAFe;<}?!2q?RBrFK5U{*ai@NLKZ^};Ul}beukveh?TQn;$%9=R+DX07m82gP$=}Uo_%&ngV`}Hyv8g{u z3SWzTGV|cwQuFIs7ZDOqO_fGf8Q`8MwL}eUp>q?4eqCmOTcwQuXtQckPy|4F1on8l zP*h>d+cH#XQf|+6c|S{7SF(Lg>bR~l(0uY?O{OEVlaxa5@e%T&xju=o1`=OD#qc16 zSvyH*my(dcp6~VqR;o(#@m44Lug@~_qw+HA=mS#Z^4reBy8iV?H~I;{LQWk3aKK8$bLRyt$g?-Artboard 46 diff --git a/tools/explorer/frontend/src/components/capacitymap/capacitymap.css b/tools/explorer/frontend/src/components/capacitymap/capacitymap.css deleted file mode 100644 index d6874448c..000000000 --- a/tools/explorer/frontend/src/components/capacitymap/capacitymap.css +++ /dev/null @@ -1,3 +0,0 @@ -.capacitymap { - -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/components/capacitymap/capacitymap.html b/tools/explorer/frontend/src/components/capacitymap/capacitymap.html deleted file mode 100644 index 5f439bd3d..000000000 --- a/tools/explorer/frontend/src/components/capacitymap/capacitymap.html +++ /dev/null @@ -1,36 +0,0 @@ -
- - - fas fa-server - Capacity finder - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/tools/explorer/frontend/src/components/capacitymap/capacitymap.js b/tools/explorer/frontend/src/components/capacitymap/capacitymap.js deleted file mode 100644 index c871fc9b4..000000000 --- a/tools/explorer/frontend/src/components/capacitymap/capacitymap.js +++ /dev/null @@ -1,61 +0,0 @@ -import capacityselector from '../capacityselector' -import nodeinfo from '../nodeinfo' -import { mapGetters, mapMutations } from 'vuex' -import { groupBy, map } from 'lodash' - -export default { - name: 'capacitymap', - components: { capacityselector, nodeinfo }, - props: [], - data () { - return { - select: { text: 'All', value: 'All' } - } - }, - computed: { - ...mapGetters(['registeredFarms', 'registeredNodes']), - allFarmsList: function () { - const allFarmers = this.registeredFarms.map(farm => { - return { - value: farm, - text: farm.name - } - }) - allFarmers.push({ text: 'All', value: 'All' }) - return allFarmers - }, - nodeLocation: function () { - // Group nodes by country - const groupedNodeLocations = groupBy( - this.registeredNodes, - node => node.location.country - ) - - const nodeLocations = [] - // Map expect type [[country, count], ...] - map(groupedNodeLocations, (groupedLocation, key) => { - const numberOfNodesInLocation = [] - const count = groupedLocation.length - numberOfNodesInLocation.push(key, count) - nodeLocations.push(numberOfNodesInLocation) - }) - - return nodeLocations - } - }, - mounted () { }, - methods: { - setSelected (value) { - if (value === 'All') { - this.$emit('custom-event-input-changed', '') - return this.setNodes(this.registeredNodes) - } - const filteredNodes = this.registeredNodes.filter( - node => node.farm_id.toString() === value.id.toString() - ) - this.setNodes(filteredNodes) - this.$emit('custom-event-input-changed', value.name.toString()) - }, - ...mapMutations(['setNodes']) - } -} diff --git a/tools/explorer/frontend/src/components/capacitymap/capacitymap.scss b/tools/explorer/frontend/src/components/capacitymap/capacitymap.scss deleted file mode 100644 index d6874448c..000000000 --- a/tools/explorer/frontend/src/components/capacitymap/capacitymap.scss +++ /dev/null @@ -1,3 +0,0 @@ -.capacitymap { - -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/components/capacitymap/capacitymap.spec.js b/tools/explorer/frontend/src/components/capacitymap/capacitymap.spec.js deleted file mode 100644 index 5c4865c1c..000000000 --- a/tools/explorer/frontend/src/components/capacitymap/capacitymap.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import Vue from 'vue' -import CapacitymapComponent from './index.vue' - -// Here are some Jasmine 2.0 tests, though you can -// use any test runner / assertion library combo you prefer -describe('CapacitymapComponent', () => { - // Inspect the raw component options - it('has a created hook', () => { - // expect(typeof CapacitymapComponent.created).toBe('function'); - }) - // Evaluate the results of functions in - // the raw component options - it('sets the correct default data', () => { - // expect(typeof CapacitymapComponent.data).toBe('function') - // const defaultData = CapacitymapComponent.data(); - // expect(defaultData.message).toBe('hello!'); - }) - // Inspect the component instance on mount - it('correctly sets the message when created', () => { - // const vm = new Vue(CapacitymapComponent).$mount(); - // expect(vm.message).toBe('bye!'); - }) - // Mount an instance and inspect the render output - it('renders the correct message', () => { - // const Ctor = Vue.extend(CapacitymapComponent); - // const vm = new Ctor().$mount(); - // expect(vm.$el.textContent).toBe('bye!'); - }) -}) diff --git a/tools/explorer/frontend/src/components/capacitymap/index.vue b/tools/explorer/frontend/src/components/capacitymap/index.vue deleted file mode 100644 index 21f18cbc1..000000000 --- a/tools/explorer/frontend/src/components/capacitymap/index.vue +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/tools/explorer/frontend/src/components/capacityselector/capacityselector.css b/tools/explorer/frontend/src/components/capacityselector/capacityselector.css deleted file mode 100644 index dbb960bb6..000000000 --- a/tools/explorer/frontend/src/components/capacityselector/capacityselector.css +++ /dev/null @@ -1,3 +0,0 @@ -.capacityselector { - -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/components/capacityselector/capacityselector.html b/tools/explorer/frontend/src/components/capacityselector/capacityselector.html deleted file mode 100644 index fbd708dc8..000000000 --- a/tools/explorer/frontend/src/components/capacityselector/capacityselector.html +++ /dev/null @@ -1,5 +0,0 @@ -
- - -
\ No newline at end of file diff --git a/tools/explorer/frontend/src/components/capacityselector/capacityselector.js b/tools/explorer/frontend/src/components/capacityselector/capacityselector.js deleted file mode 100644 index ebc035bc0..000000000 --- a/tools/explorer/frontend/src/components/capacityselector/capacityselector.js +++ /dev/null @@ -1,54 +0,0 @@ -import { mapGetters, mapMutations } from 'vuex' -export default { - name: 'capacityselector', - components: {}, - props: ['icon', 'label'], - data () { - let max = 0 - let range = [] - switch (this.label) { - case 'CRU': { - max = 64 - range = [0, 58] - break - } - case 'HRU': { - max = 5000 - range = [0, 4500] - break - } - case 'MRU': { - max = 512 - range = [0, 460] - break - } - case 'SRU': { - max = 5000 - range = [0, 4500] - break - } - } - return { - min: 0, - max, - slider: 40, - range - } - }, - computed: { - ...mapGetters(['registeredNodes']) - }, - methods: { - handleInput (value) { - const [min, max] = value - let filteredNodes = this.registeredNodes.filter( - node => - node.total_resources[this.label.toLowerCase()] <= max && - node.total_resources[this.label.toLowerCase()] >= min - ) - - this.setNodes(filteredNodes) - }, - ...mapMutations(['setNodes']) - } -} diff --git a/tools/explorer/frontend/src/components/capacityselector/capacityselector.scss b/tools/explorer/frontend/src/components/capacityselector/capacityselector.scss deleted file mode 100644 index dbb960bb6..000000000 --- a/tools/explorer/frontend/src/components/capacityselector/capacityselector.scss +++ /dev/null @@ -1,3 +0,0 @@ -.capacityselector { - -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/components/capacityselector/capacityselector.spec.js b/tools/explorer/frontend/src/components/capacityselector/capacityselector.spec.js deleted file mode 100644 index 84683220d..000000000 --- a/tools/explorer/frontend/src/components/capacityselector/capacityselector.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import Vue from 'vue' -import CapacityselectorComponent from './index.vue' - -// Here are some Jasmine 2.0 tests, though you can -// use any test runner / assertion library combo you prefer -describe('CapacityselectorComponent', () => { - // Inspect the raw component options - it('has a created hook', () => { - // expect(typeof CapacityselectorComponent.created).toBe('function'); - }) - // Evaluate the results of functions in - // the raw component options - it('sets the correct default data', () => { - // expect(typeof CapacityselectorComponent.data).toBe('function') - // const defaultData = CapacityselectorComponent.data(); - // expect(defaultData.message).toBe('hello!'); - }) - // Inspect the component instance on mount - it('correctly sets the message when created', () => { - // const vm = new Vue(CapacityselectorComponent).$mount(); - // expect(vm.message).toBe('bye!'); - }) - // Mount an instance and inspect the render output - it('renders the correct message', () => { - // const Ctor = Vue.extend(CapacityselectorComponent); - // const vm = new Ctor().$mount(); - // expect(vm.$el.textContent).toBe('bye!'); - }) -}) diff --git a/tools/explorer/frontend/src/components/capacityselector/index.vue b/tools/explorer/frontend/src/components/capacityselector/index.vue deleted file mode 100644 index 9723657dd..000000000 --- a/tools/explorer/frontend/src/components/capacityselector/index.vue +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/tools/explorer/frontend/src/components/minigraph/index.vue b/tools/explorer/frontend/src/components/minigraph/index.vue deleted file mode 100644 index e5cb3a8f1..000000000 --- a/tools/explorer/frontend/src/components/minigraph/index.vue +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/tools/explorer/frontend/src/components/minigraph/minigraph.css b/tools/explorer/frontend/src/components/minigraph/minigraph.css deleted file mode 100644 index 0d0b020bc..000000000 --- a/tools/explorer/frontend/src/components/minigraph/minigraph.css +++ /dev/null @@ -1,3 +0,0 @@ -.minigraph { - width: 100%; -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/components/minigraph/minigraph.html b/tools/explorer/frontend/src/components/minigraph/minigraph.html deleted file mode 100644 index 0f39b6396..000000000 --- a/tools/explorer/frontend/src/components/minigraph/minigraph.html +++ /dev/null @@ -1,18 +0,0 @@ -
- - - - {{val}} {{byte_head}} - {{title}} - - - - - - - {{val}} {{byte_head}} - {{title}} - - - -
\ No newline at end of file diff --git a/tools/explorer/frontend/src/components/minigraph/minigraph.js b/tools/explorer/frontend/src/components/minigraph/minigraph.js deleted file mode 100644 index 5e75e64ae..000000000 --- a/tools/explorer/frontend/src/components/minigraph/minigraph.js +++ /dev/null @@ -1,55 +0,0 @@ -export default { - name: 'minigraph', - components: {}, - props: { - color: { - type: String, - default: 'secondary darken-2 ' - }, - title: { - type: String, - default: '' - }, - value: { - default: '0' - }, - append: { - type: String, - default: '' - }, - special: { - type: Boolean - }, - clickable: { - type: Boolean, - default: false - } - }, - watch: { - value: function (newVal) { - this.val = newVal - this.byte_head = this.append - while (this.byte_heads.includes(this.byte_head) && this.val > 9999) { - var index = this.byte_heads.indexOf(this.byte_head) - if (index < this.byte_heads.length) { - this.byte_head = this.byte_heads[index + 1] - this.val /= 1000 - this.val = Math.round((this.val + Number.EPSILON) * 100) / 100 - } - } - } - }, - mounted () { }, - data () { - return { - data: [], - byte_heads: ['GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], - val: '0', - byte_head: '' - } - }, - methods: { - adaptUnit () { } - }, - computed: {} -} diff --git a/tools/explorer/frontend/src/components/minigraph/minigraph.scss b/tools/explorer/frontend/src/components/minigraph/minigraph.scss deleted file mode 100644 index 0d0b020bc..000000000 --- a/tools/explorer/frontend/src/components/minigraph/minigraph.scss +++ /dev/null @@ -1,3 +0,0 @@ -.minigraph { - width: 100%; -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/components/minigraph/minigraph.spec.js b/tools/explorer/frontend/src/components/minigraph/minigraph.spec.js deleted file mode 100644 index 7a3151452..000000000 --- a/tools/explorer/frontend/src/components/minigraph/minigraph.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import Vue from 'vue' -import MinigraphComponent from './index.vue' - -// Here are some Jasmine 2.0 tests, though you can -// use any test runner / assertion library combo you prefer -describe('MinigraphComponent', () => { - // Inspect the raw component options - it('has a created hook', () => { - // expect(typeof MinigraphComponent.created).toBe('function'); - }) - // Evaluate the results of functions in - // the raw component options - it('sets the correct default data', () => { - // expect(typeof MinigraphComponent.data).toBe('function') - // const defaultData = MinigraphComponent.data(); - // expect(defaultData.message).toBe('hello!'); - }) - // Inspect the component instance on mount - it('correctly sets the message when created', () => { - // const vm = new Vue(MinigraphComponent).$mount(); - // expect(vm.message).toBe('bye!'); - }) - // Mount an instance and inspect the render output - it('renders the correct message', () => { - // const Ctor = Vue.extend(MinigraphComponent); - // const vm = new Ctor().$mount(); - // expect(vm.$el.textContent).toBe('bye!'); - }) -}) diff --git a/tools/explorer/frontend/src/components/nodeinfo/index.vue b/tools/explorer/frontend/src/components/nodeinfo/index.vue deleted file mode 100644 index 768863223..000000000 --- a/tools/explorer/frontend/src/components/nodeinfo/index.vue +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.css b/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.css deleted file mode 100644 index 1c9c8d72a..000000000 --- a/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.css +++ /dev/null @@ -1,7 +0,0 @@ -.nodeinfo { - width: 100%; -} - -span > a { - color: white; -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.html b/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.html deleted file mode 100644 index 9335347a2..000000000 --- a/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.html +++ /dev/null @@ -1,132 +0,0 @@ -
- -
- fa-info-circle - Node Information -
- - - Node ID - - {{node.id}} - - - - Node version - - {{node.version}} - - - - Farmer - - {{node.farm_name}} ({{node.farm_id}}) - - - - Location - - {{node.location.country}}, {{node.location.city}} - - - - Uptime - - {{node.uptime}} - - - - Status - - {{node.status.status}} - - - - Updated - - {{node.updated}} - - - - - - - When free to use is true, you can paid for this node capacity using FreeTFT - - - - -
- -
- fa-chart-barProvisioned workloads -
- - - - - Network: {{node.workloads.network}} - - - 0-DB namespace: {{node.workloads.zdb_namespace}} - - - - - Volume: {{node.workloads.volume}} - - - Container: {{node.workloads.container}} - - - - - Kubernetes VM: {{node.workloads.k8s_vm}} - - - - - -
- - -
- fa-chart-pieResource units reserved -
- - - - - {{ key }} - - - - - - - Total: {{ node.totalResources[key] }} - - - Reserved: {{ node.reservedResources[key] }} - - - - - - -
- - -
\ No newline at end of file diff --git a/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.js b/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.js deleted file mode 100644 index 47821aa42..000000000 --- a/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.js +++ /dev/null @@ -1,17 +0,0 @@ -export default { - name: 'nodeinfo', - props: ['node'], - data () { - return { - freeIcon: this.node.freeToUse === true ? { icon: 'fa-check', color: 'green' } : { icon: 'fa-times', color: 'red' } - } - }, - mounted () { - console.log(this.node) - }, - methods: { - getPercentage (type) { - return (this.node.reservedResources[type] / this.node.totalResources[type]) * 100 - } - } -} diff --git a/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.scss b/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.scss deleted file mode 100644 index b3bc3991c..000000000 --- a/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.scss +++ /dev/null @@ -1,3 +0,0 @@ -.nodeinfo { - width: 100%; -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.spec.js b/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.spec.js deleted file mode 100644 index c816dc9ef..000000000 --- a/tools/explorer/frontend/src/components/nodeinfo/nodeinfo.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import Vue from 'vue' -import NodeinfoComponent from './index.vue' - -// Here are some Jasmine 2.0 tests, though you can -// use any test runner / assertion library combo you prefer -describe('NodeinfoComponent', () => { - // Inspect the raw component options - it('has a created hook', () => { - // expect(typeof NodeinfoComponent.created).toBe('function'); - }) - // Evaluate the results of functions in - // the raw component options - it('sets the correct default data', () => { - // expect(typeof NodeinfoComponent.data).toBe('function') - // const defaultData = NodeinfoComponent.data(); - // expect(defaultData.message).toBe('hello!'); - }) - // Inspect the component instance on mount - it('correctly sets the message when created', () => { - // const vm = new Vue(NodeinfoComponent).$mount(); - // expect(vm.message).toBe('bye!'); - }) - // Mount an instance and inspect the render output - it('renders the correct message', () => { - // const Ctor = Vue.extend(NodeinfoComponent); - // const vm = new Ctor().$mount(); - // expect(vm.$el.textContent).toBe('bye!'); - }) -}) diff --git a/tools/explorer/frontend/src/components/nodestable/index.vue b/tools/explorer/frontend/src/components/nodestable/index.vue deleted file mode 100644 index d62289953..000000000 --- a/tools/explorer/frontend/src/components/nodestable/index.vue +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/tools/explorer/frontend/src/components/nodestable/nodestable.css b/tools/explorer/frontend/src/components/nodestable/nodestable.css deleted file mode 100644 index 5802e0ba8..000000000 --- a/tools/explorer/frontend/src/components/nodestable/nodestable.css +++ /dev/null @@ -1,4 +0,0 @@ -.nodestable { - - -} diff --git a/tools/explorer/frontend/src/components/nodestable/nodestable.html b/tools/explorer/frontend/src/components/nodestable/nodestable.html deleted file mode 100644 index f0295d8d0..000000000 --- a/tools/explorer/frontend/src/components/nodestable/nodestable.html +++ /dev/null @@ -1,79 +0,0 @@ - -
- - - fas fa-server -
- {{ farmselected.name }} -
- - - - - - - - - Show offline nodes - - - - - Full screen - - -
- - - - - - - - - - - - - - - - - - -
-
diff --git a/tools/explorer/frontend/src/components/nodestable/nodestable.js b/tools/explorer/frontend/src/components/nodestable/nodestable.js deleted file mode 100644 index a0d17d42c..000000000 --- a/tools/explorer/frontend/src/components/nodestable/nodestable.js +++ /dev/null @@ -1,127 +0,0 @@ -import nodeInfo from '../nodeinfo' -import { mapGetters, mapActions } from 'vuex' -import moment from 'moment' -import momentDurationFormatSetup from 'moment-duration-format' -import { find } from 'lodash' - -momentDurationFormatSetup(moment) - -export default { - name: 'nodestable', - props: ['farmselected', 'registerednodes'], - - components: { nodeInfo }, - data () { - return { - searchnodes: undefined, - showOffline: false, - storeName: '', - showDialog: false, - dilogTitle: 'title', - dialogBody: '', - dialogActions: [], - dialogImage: null, - block: null, - showBadge: true, - menu: false, - loadedNodes: false, - othersHidden: false, - itemsPerPage: 4, - expanded: [], - - headers: [ - { text: 'ID', value: 'id' }, - { text: 'Uptime', value: 'uptime' }, - { text: 'Version', value: 'version' }, - { text: 'Farmer', value: 'farm_name' }, - { text: 'Status', value: 'status', align: 'center' } - ] - } - }, - computed: { - ...mapGetters(['registeredFarms', 'nodes']), - // Parse nodelist to table format here - parsedNodesList: function () { - const nodeList = this.nodes ? this.nodes : this.registerednodes - const parsedNodes = nodeList.filter(node => this.showNode(node)).map(node => { - const farm = find(this.registeredFarms, farmer => { - return farmer.id === node.farm_id - }) - - return { - uptime: moment.duration(node.uptime, 'seconds').format(), - version: node.os_version, - id: node.node_id, - farm_name: farm ? farm.name : node.farm_id, - farm_id: node.farm_id, - name: 'node ' + node.node_id, - totalResources: node.total_resources, - reservedResources: node.reserved_resources, - usedResources: node.used_resources, - workloads: node.workloads, - updated: new Date(node.updated * 1000), - status: this.getStatus(node), - location: node.location, - freeToUse: node.free_to_use - } - }) - return parsedNodes - } - }, - mounted () { - this.resetNodes() - }, - methods: { - ...mapActions(['resetNodes']), - getStatus (node) { - const { updated } = node - const startTime = moment() - const end = moment.unix(updated) - const minutes = startTime.diff(end, 'minutes') - - // if updated difference in minutes with now is less then 10 minutes, node is up - if (minutes < 15) return { color: 'green', status: 'up' } - else if (minutes > 16 && minutes < 20) { return { color: 'orange', status: 'likely down' } } else return { color: 'red', status: 'down' } - }, - showNode (node) { - if (this.farmselected && this.farmselected.id !== node.farm_id) { - return false - } - if (!this.showOffline && this.getStatus(node)['status'] === 'down') { - return false - } - - return true - }, - truncateString (str) { - // do not truncate in full screen mode - if (this.othersHidden === true) { - return str - } - str = str.toString() - if (str.length < 10) return str - return str.substr(0, 10) + '...' - }, - openNodeDetails (node) { - const index = this.expanded.indexOf(node) - if (index > -1) this.expanded.splice(index, 1) - else this.expanded.push(node) - }, - hideOthers () { - var all = document.getElementsByClassName('others') - for (var i = 0; i < all.length; i++) { - all[i].style.display = 'none' - all[i].classList.remove('flex') - } - this.othersHidden = true - }, - showOthers () { - var all = document.getElementsByClassName('others') - for (var i = 0; i < all.length; i++) { - all[i].style.display = 'block' - all[i].classList.add('flex') - } - this.othersHidden = false - } - } -} diff --git a/tools/explorer/frontend/src/components/nodestable/nodestable.scss b/tools/explorer/frontend/src/components/nodestable/nodestable.scss deleted file mode 100644 index 97dffe7cd..000000000 --- a/tools/explorer/frontend/src/components/nodestable/nodestable.scss +++ /dev/null @@ -1,3 +0,0 @@ -.nodestable { - -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/components/nodestable/nodestable.spec.js b/tools/explorer/frontend/src/components/nodestable/nodestable.spec.js deleted file mode 100644 index 3b4225ea2..000000000 --- a/tools/explorer/frontend/src/components/nodestable/nodestable.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import Vue from 'vue' -import NodestableComponent from './index.vue' - -// Here are some Jasmine 2.0 tests, though you can -// use any test runner / assertion library combo you prefer -describe('NodestableComponent', () => { - // Inspect the raw component options - it('has a created hook', () => { - // expect(typeof NodestableComponent.created).toBe('function'); - }) - // Evaluate the results of functions in - // the raw component options - it('sets the correct default data', () => { - // expect(typeof NodestableComponent.data).toBe('function') - // const defaultData = NodestableComponent.data(); - // expect(defaultData.message).toBe('hello!'); - }) - // Inspect the component instance on mount - it('correctly sets the message when created', () => { - // const vm = new Vue(NodestableComponent).$mount(); - // expect(vm.message).toBe('bye!'); - }) - // Mount an instance and inspect the render output - it('renders the correct message', () => { - // const Ctor = Vue.extend(NodestableComponent); - // const vm = new Ctor().$mount(); - // expect(vm.$el.textContent).toBe('bye!'); - }) -}) diff --git a/tools/explorer/frontend/src/components/scrollablecard/index.vue b/tools/explorer/frontend/src/components/scrollablecard/index.vue deleted file mode 100644 index 2aec72a4d..000000000 --- a/tools/explorer/frontend/src/components/scrollablecard/index.vue +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.css b/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.css deleted file mode 100644 index 0abd32ac4..000000000 --- a/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.css +++ /dev/null @@ -1,3 +0,0 @@ -.scrollablecard { - -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.html b/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.html deleted file mode 100644 index 7bfb392e1..000000000 --- a/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.html +++ /dev/null @@ -1,31 +0,0 @@ -
- - - {{icon}} - {{title}} - - - - -
-
- - - - - - fas fa-exchange-alt - - -
Trade info
-
Even more info
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file diff --git a/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.js b/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.js deleted file mode 100644 index 545ec02f5..000000000 --- a/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.js +++ /dev/null @@ -1,23 +0,0 @@ -export default { - name: 'scrollablecard', - components: {}, - props: [ - 'title', - 'icon', - 'disableSearch' - ], - data () { - return { - - } - }, - computed: { - - }, - mounted () { - - }, - methods: { - - } -} diff --git a/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.scss b/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.scss deleted file mode 100644 index 0abd32ac4..000000000 --- a/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.scss +++ /dev/null @@ -1,3 +0,0 @@ -.scrollablecard { - -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.spec.js b/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.spec.js deleted file mode 100644 index a366e4123..000000000 --- a/tools/explorer/frontend/src/components/scrollablecard/scrollablecard.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import Vue from 'vue' -import ScrollablecardComponent from './index.vue' - -// Here are some Jasmine 2.0 tests, though you can -// use any test runner / assertion library combo you prefer -describe('ScrollablecardComponent', () => { - // Inspect the raw component options - it('has a created hook', () => { - // expect(typeof ScrollablecardComponent.created).toBe('function'); - }) - // Evaluate the results of functions in - // the raw component options - it('sets the correct default data', () => { - // expect(typeof ScrollablecardComponent.data).toBe('function') - // const defaultData = ScrollablecardComponent.data(); - // expect(defaultData.message).toBe('hello!'); - }) - // Inspect the component instance on mount - it('correctly sets the message when created', () => { - // const vm = new Vue(ScrollablecardComponent).$mount(); - // expect(vm.message).toBe('bye!'); - }) - // Mount an instance and inspect the render output - it('renders the correct message', () => { - // const Ctor = Vue.extend(ScrollablecardComponent); - // const vm = new Ctor().$mount(); - // expect(vm.$el.textContent).toBe('bye!'); - }) -}) diff --git a/tools/explorer/frontend/src/main.js b/tools/explorer/frontend/src/main.js deleted file mode 100644 index 0f67469b9..000000000 --- a/tools/explorer/frontend/src/main.js +++ /dev/null @@ -1,15 +0,0 @@ -import Vue from 'vue' -import App from './App.vue' -import router from './router' -import store from './store' -import vuetify from './plugins/vuetify' -import './plugins' - -Vue.config.productionTip = false - -new Vue({ - router, - store, - vuetify, - render: h => h(App) -}).$mount('#app') diff --git a/tools/explorer/frontend/src/plugins/chartkick.js b/tools/explorer/frontend/src/plugins/chartkick.js deleted file mode 100644 index 4a5e757cd..000000000 --- a/tools/explorer/frontend/src/plugins/chartkick.js +++ /dev/null @@ -1,5 +0,0 @@ -import Vue from 'vue' -import chartkick from 'vue-chartkick' - -chartkick.configure({ language: 'en', mapsApiKey: 'AIzaSyC7lDq7-_tCyOW4_vR90gOl5lHXRlgtUCM' }) -Vue.use(chartkick) diff --git a/tools/explorer/frontend/src/plugins/index.js b/tools/explorer/frontend/src/plugins/index.js deleted file mode 100644 index e178ae989..000000000 --- a/tools/explorer/frontend/src/plugins/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import './chartkick' -import './maps' diff --git a/tools/explorer/frontend/src/plugins/maps.js b/tools/explorer/frontend/src/plugins/maps.js deleted file mode 100644 index 1cd1e1dc5..000000000 --- a/tools/explorer/frontend/src/plugins/maps.js +++ /dev/null @@ -1,7 +0,0 @@ -import Vue from 'vue' -import * as VueGoogleMaps from 'vue2-google-maps' -Vue.use(VueGoogleMaps, { - load: { - key: 'AIzaSyC7lDq7-_tCyOW4_vR90gOl5lHXRlgtUCM' - } -}) diff --git a/tools/explorer/frontend/src/plugins/vuetify.js b/tools/explorer/frontend/src/plugins/vuetify.js deleted file mode 100644 index 0cd78623f..000000000 --- a/tools/explorer/frontend/src/plugins/vuetify.js +++ /dev/null @@ -1,17 +0,0 @@ -import Vue from 'vue' -import Vuetify from 'vuetify' -import 'vuetify/src/styles/main.sass' -import '@fortawesome/fontawesome-free/css/all.css' -Vue.use(Vuetify) - -export default new Vuetify({ - iconfont: 'fa', - theme: { - themes: { - light: { - primary: '#2d4052', - secondary: '#57be8e' - } - } - } -}) diff --git a/tools/explorer/frontend/src/router.js b/tools/explorer/frontend/src/router.js deleted file mode 100644 index 4de1c6a3d..000000000 --- a/tools/explorer/frontend/src/router.js +++ /dev/null @@ -1,22 +0,0 @@ -import Vue from 'vue' -import Router from 'vue-router' -import { publicPath } from '../vue.config' - -Vue.use(Router) - -export default new Router({ - mode: 'history', - base: publicPath, - routes: [ - { - path: '/', - name: 'Capacity directory', - component: () => import(/* webpackChunkName: "capacity-page" */ './views/capacity'), - meta: { - icon: 'fas fa-server', - position: 'top', - displayName: 'Capacity' - } - } - ] -}) diff --git a/tools/explorer/frontend/src/services/tfService.js b/tools/explorer/frontend/src/services/tfService.js deleted file mode 100644 index cc0dd7139..000000000 --- a/tools/explorer/frontend/src/services/tfService.js +++ /dev/null @@ -1,65 +0,0 @@ -import axios from 'axios' -import config from '../../public/config' - -export default { - getName () { - return axios.post(`/${window.config.identityActor}/threebot_name`, { - args: {} - }) - }, - getUser (name) { - return axios.get(`${config.tfApiUrl}/users`, { - params: { - name: name - } - }) - }, - getFarms (userId) { - return axios.get(`${config.tfApiUrl}/farms`, { - params: { - threebot_id: userId - } - }) - }, - registerFarm (farm) { - return axios.post(`${config.tfApiUrl}/farms/register`, - { - farm: farm - } - ) - }, - updateFarm (farmId, farm) { - return axios.post(`${config.tfApiUrl}/farms/update`, { - args: { - farm_id: farmId, - farm: farm - } - }) - }, - getNodes (farm_id = undefined, size, page) { - return axios.get(`${config.tfApiUrl}/nodes`, { - params: { - farm_id: farm_id, - size, - page - } - }) - }, - registeredfarms (size, page) { - return axios.get(`${config.tfApiUrl}/farms`, { - params: { - size, - page - } - }) - }, - news () { - return axios.get(`${config.tfApiUrl}/news`) - }, - getExplorerConstants () { - return axios.get(`${config.tfExplorerUrl}`) - }, - getExplorerBlockByHeight (height) { - return axios.get(`${config.tfExplorerUrl}/blocks/${height}`) - } -} diff --git a/tools/explorer/frontend/src/store/index.js b/tools/explorer/frontend/src/store/index.js deleted file mode 100644 index 30541cc11..000000000 --- a/tools/explorer/frontend/src/store/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import Vue from 'vue' -import Vuex from 'vuex' -import transactionStore from './transaction' - -Vue.use(Vuex) - -export default new Vuex.Store({ - modules: { - transactionStore - } -}) diff --git a/tools/explorer/frontend/src/store/transaction.js b/tools/explorer/frontend/src/store/transaction.js deleted file mode 100644 index 4ed4b3194..000000000 --- a/tools/explorer/frontend/src/store/transaction.js +++ /dev/null @@ -1,146 +0,0 @@ -import tfService from '../services/tfService' -import lodash from 'lodash' - -export default ({ - state: { - user: {}, - registeredNodes: [], - nodePage: 2, - farmPage: 2, - nodes: undefined, - registeredFarms: [], - farms: [], - nodeSpecs: { - amountregisteredNodes: 0, - amountregisteredFarms: 0, - countries: 0, - onlinenodes: 0, - cru: 0, - mru: 0, - sru: 0, - hru: 0, - network: 0, - volume: 0, - container: 0, - zdb_namespace: 0, - k8s_vm: 0 - - } - }, - actions: { - getName: async context => { - var response = await tfService.getName() - return response.data.name - }, - getUser: async context => { - var name = await context.dispatch('getName') - var response = await tfService.getUser(name) - context.commit('setUser', response.data) - }, - getRegisteredNodes (context, params) { - // if state.page is undefined, means we reached an endstate and fetched all the nodes already - if (context.state.nodePage === undefined) { - return - } - - let page = params.page || context.state.nodePage - - tfService.getNodes(undefined, params.size, page).then(response => { - context.commit('setRegisteredNodes', response) - context.commit('setTotalSpecs', response.data) - }) - }, - getRegisteredFarms (context, params) { - // if state.page is undefined, means we reached an endstate and fetched all the nodes already - if (context.state.farmPage === undefined) { - return - } - - let page = params.page || context.state.farmPage - - tfService.registeredfarms(params.size, page).then(response => { - context.commit('setAmountOfFarms', response.data) - context.commit('setRegisteredFarms', response) - }) - }, - getFarms: context => { - tfService.getFarms(context.getters.user.id).then(response => { - context.commit('setFarms', response.data) - }) - }, - resetNodes: context => { - context.commit('setNodes', undefined) - } - }, - mutations: { - setRegisteredNodes (state, response) { - if (response.data.length === 0) { - state.nodePage = undefined - return - } - state.registeredNodes = state.registeredNodes.concat(response.data) - state.nodePage += 1 - }, - setRegisteredFarms (state, response) { - // state.registeredFarms = value - if (response.data.length === 0) { - state.farmPage = undefined - return - } - state.registeredFarms = state.registeredFarms.concat(response.data) - state.farmPage += 1 - }, - setFarms (state, value) { - state.farms = value - }, - setNodes (state, value) { - state.nodes = value - }, - setUser: (state, user) => { - state.user = user - }, - setAmountOfFarms (state, value) { - state.nodeSpecs.amountregisteredFarms = value.length - }, - setTotalSpecs (state, value) { - if (value.length === 0) { - return - } - state.nodeSpecs.amountregisteredNodes += value.length - state.nodeSpecs.onlinenodes += countOnlineNodes(value) - state.nodeSpecs.countries += lodash.uniqBy( - value, - node => node.location.country - ).length - state.nodeSpecs.cru += lodash.sumBy(value, node => node.total_resources.cru) - state.nodeSpecs.mru += lodash.sumBy(value, node => node.total_resources.mru) - state.nodeSpecs.sru += lodash.sumBy(value, node => node.total_resources.sru) - state.nodeSpecs.hru += lodash.sumBy(value, node => node.total_resources.hru) - state.nodeSpecs.network += lodash.sumBy(value, node => node.workloads.network) - state.nodeSpecs.volume += lodash.sumBy(value, node => node.workloads.volume) - state.nodeSpecs.container += lodash.sumBy(value, node => node.workloads.container) - state.nodeSpecs.zdb_namespace += lodash.sumBy(value, node => node.workloads.zdb_namespace) - state.nodeSpecs.k8s_vm += lodash.sumBy(value, node => node.workloads.k8s_vm) - } - }, - getters: { - user: state => state.user, - registeredNodes: state => state.registeredNodes, - nodes: state => state.nodes, - registeredFarms: state => state.registeredFarms, - farms: state => state.farms, - nodeSpecs: state => state.nodeSpecs, - nodePage: state => state.nodePage, - farmPage: state => state.farmPage - } -}) - -function countOnlineNodes (data) { - let onlinecounter = 0 - data.forEach(node => { - const timestamp = new Date().getTime() / 1000 - const minutes = (timestamp - node.updated) / 60 - if (minutes < 20) onlinecounter++ - }) - return onlinecounter -} diff --git a/tools/explorer/frontend/src/views/capacity/capacity.css b/tools/explorer/frontend/src/views/capacity/capacity.css deleted file mode 100644 index 8341fb850..000000000 --- a/tools/explorer/frontend/src/views/capacity/capacity.css +++ /dev/null @@ -1,19 +0,0 @@ -.capacity { - height: 100%; -} -.content { - background: #fafafa !important; -} -.topround { - border-radius: 4px 4px 0 0 !important; -} -.rounded { - border-radius: 0 4px 4px 0 !important; -} -.v-menu__content, -.v-card { - border-radius: 4px !important; -} -.v-card__title { - font-size: 18px !important; -} diff --git a/tools/explorer/frontend/src/views/capacity/capacity.html b/tools/explorer/frontend/src/views/capacity/capacity.html deleted file mode 100644 index 439eb69d1..000000000 --- a/tools/explorer/frontend/src/views/capacity/capacity.html +++ /dev/null @@ -1,67 +0,0 @@ -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
\ No newline at end of file diff --git a/tools/explorer/frontend/src/views/capacity/capacity.js b/tools/explorer/frontend/src/views/capacity/capacity.js deleted file mode 100644 index 869f2a191..000000000 --- a/tools/explorer/frontend/src/views/capacity/capacity.js +++ /dev/null @@ -1,63 +0,0 @@ -import miniGraph from '../../components/minigraph' -import capacityMap from '../../components/capacitymap' -import nodesTable from '../../components/nodestable' -import scrollablecard from '../../components/scrollablecard' -import { mapGetters, mapActions } from 'vuex' - -export default { - name: 'capacity', - components: { miniGraph, capacityMap, nodesTable, scrollablecard }, - props: [], - data () { - return { - showDialog: false, - dilogTitle: 'title', - dialogBody: '', - dialogActions: [], - dialogImage: null, - block: null, - showBadge: true, - menu: false, - selectedNode: '' - } - }, - computed: { - ...mapGetters([ - 'nodeSpecs', - 'registeredNodes' - ]) - }, - mounted () { - this.getRegisteredNodes({ size: 100, page: 1 }) - this.getRegisteredFarms({ size: 100, page: 1 }) - this.initialiseNodesLoading() - this.initialiseFarmsLoading() - // this.initialiseRefresh() - }, - - methods: { - ...mapActions(['getRegisteredNodes', 'getRegisteredFarms']), - changeSelectedNode (data) { - this.selectedNode = data - }, - initialiseRefresh () { - const that = this - this.refreshInterval = setInterval(() => { - that.getRegisteredNodes({ size: 100, page: 1 }) - that.getRegisteredFarms({ size: 100, page: 1 }) - }, 60000) // refresh every 10 minutes - }, - initialiseNodesLoading () { - const that = this - this.nodesLoadingInterval = setInterval(() => { - that.getRegisteredNodes({ size: 50, page: undefined }) - }, 500) - }, - initialiseFarmsLoading () { - const that = this - this.farmsLoadingInterval = setInterval(() => { - that.getRegisteredFarms({ size: 50, page: undefined }) - }, 500) - } - } -} diff --git a/tools/explorer/frontend/src/views/capacity/capacity.scss b/tools/explorer/frontend/src/views/capacity/capacity.scss deleted file mode 100644 index c801404f6..000000000 --- a/tools/explorer/frontend/src/views/capacity/capacity.scss +++ /dev/null @@ -1,3 +0,0 @@ -.capacity { - height: 100%; -} \ No newline at end of file diff --git a/tools/explorer/frontend/src/views/capacity/capacity.spec.js b/tools/explorer/frontend/src/views/capacity/capacity.spec.js deleted file mode 100644 index d8368f804..000000000 --- a/tools/explorer/frontend/src/views/capacity/capacity.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import Vue from 'vue' -import CapacityComponent from './index.vue' - -// Here are some Jasmine 2.0 tests, though you can -// use any test runner / assertion library combo you prefer -describe('CapacityComponent', () => { - // Inspect the raw component options - it('has a created hook', () => { - // expect(typeof CapacityComponent.created).toBe('function'); - }) - // Evaluate the results of functions in - // the raw component options - it('sets the correct default data', () => { - // expect(typeof CapacityComponent.data).toBe('function') - // const defaultData = CapacityComponent.data(); - // expect(defaultData.message).toBe('hello!'); - }) - // Inspect the component instance on mount - it('correctly sets the message when created', () => { - // const vm = new Vue(CapacityComponent).$mount(); - // expect(vm.message).toBe('bye!'); - }) - // Mount an instance and inspect the render output - it('renders the correct message', () => { - // const Ctor = Vue.extend(CapacityComponent); - // const vm = new Ctor().$mount(); - // expect(vm.$el.textContent).toBe('bye!'); - }) -}) diff --git a/tools/explorer/frontend/src/views/capacity/index.vue b/tools/explorer/frontend/src/views/capacity/index.vue deleted file mode 100644 index 192a1da8b..000000000 --- a/tools/explorer/frontend/src/views/capacity/index.vue +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/tools/explorer/frontend/vue.config.js b/tools/explorer/frontend/vue.config.js deleted file mode 100644 index 718176cdb..000000000 --- a/tools/explorer/frontend/vue.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - publicPath: process.env.NODE_ENV === 'production' - ? '/public' - : '/' -} diff --git a/tools/explorer/main.go b/tools/explorer/main.go deleted file mode 100644 index 44c119544..000000000 --- a/tools/explorer/main.go +++ /dev/null @@ -1,234 +0,0 @@ -//go:generate $GOPATH/bin/statik -f -src=./frontend/dist -package main - -import ( - "context" - "flag" - "fmt" - "io" - "strings" - - "net/http" - "os" - "os/signal" - "time" - - "github.com/rusart/muxprom" - - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/rs/zerolog/log" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/rakyll/statik/fs" - "github.com/threefoldtech/zos/pkg/app" - "github.com/threefoldtech/zos/pkg/version" - "github.com/threefoldtech/zos/tools/explorer/config" - "github.com/threefoldtech/zos/tools/explorer/mw" - "github.com/threefoldtech/zos/tools/explorer/pkg/directory" - "github.com/threefoldtech/zos/tools/explorer/pkg/escrow" - escrowdb "github.com/threefoldtech/zos/tools/explorer/pkg/escrow/types" - "github.com/threefoldtech/zos/tools/explorer/pkg/phonebook" - "github.com/threefoldtech/zos/tools/explorer/pkg/stellar" - "github.com/threefoldtech/zos/tools/explorer/pkg/workloads" - _ "github.com/threefoldtech/zos/tools/explorer/statik" -) - -// Pkg is a shorthand type for func -type Pkg func(*mux.Router, *mongo.Database) error - -func main() { - app.Initialize() - - var ( - listen string - dbConf string - dbName string - seed string - foundationAddress string - ver bool - flushEscrows bool - backupSigners stellar.Signers - ) - - flag.StringVar(&listen, "listen", ":8080", "listen address, default :8080") - flag.StringVar(&dbConf, "mongo", "mongodb://localhost:27017", "connection string to mongo database") - flag.StringVar(&dbName, "name", "explorer", "database name") - flag.StringVar(&seed, "seed", "", "wallet seed") - flag.StringVar(&config.Config.Network, "network", "", "tfchain network") - flag.StringVar(&foundationAddress, "foundation-address", "", "foundation address for the escrow foundation payment cut, if not set and the foundation should receive a cut from a resersvation payment, the wallet seed will receive the payment instead") - flag.BoolVar(&ver, "v", false, "show version and exit") - flag.Var(&backupSigners, "backupsigner", "reusable flag which adds a signer to the escrow accounts, we need atleast 5 signers to activate multisig") - flag.BoolVar(&flushEscrows, "flush-escrows", false, "flush all escrows in the database, including currently active ones, and their associated addressses") - - flag.Parse() - - if ver { - version.ShowAndExit(false) - } - - if err := config.Valid(); err != nil { - log.Fatal().Err(err).Msg("invalid configuration") - } - - dropEscrow := false - - if flushEscrows { - dropEscrow = userInputYesNo("Are you sure you want to drop all escrows and related addresses?") && - userInputYesNo("Are you REALLY sure you want to drop all escrows and related addresses?") - } - - if flushEscrows && !dropEscrow { - log.Fatal().Msg("user indicated he does not want to remove existing escrow information - please restart the explorer without the \"flush-escrows\" flag") - } - - ctx := context.Background() - client, err := connectDB(ctx, dbConf) - if err != nil { - log.Fatal().Err(err).Msg("fail to connect to database") - } - - s, err := createServer(listen, dbName, client, seed, foundationAddress, dropEscrow, backupSigners) - if err != nil { - log.Fatal().Err(err).Msg("fail to create HTTP server") - } - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - - go s.ListenAndServe() - - <-c - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - - if err := s.Shutdown(ctx); err != nil { - log.Printf("error during server shutdown: %v\n", err) - } -} - -func connectDB(ctx context.Context, connectionURI string) (*mongo.Client, error) { - client, err := mongo.NewClient(options.Client().ApplyURI(connectionURI)) - if err != nil { - return nil, err - } - - if err := client.Connect(ctx); err != nil { - return nil, err - } - - return client, nil -} - -func createServer(listen, dbName string, client *mongo.Client, seed string, foundationAddress string, dropEscrowData bool, backupSigners stellar.Signers) (*http.Server, error) { - db, err := mw.NewDatabaseMiddleware(dbName, client) - if err != nil { - return nil, err - } - - var prom *muxprom.MuxProm - router := mux.NewRouter() - statikFS, err := fs.New() - if err != nil { - return nil, err - } - - prom = muxprom.New( - muxprom.Router(router), - muxprom.Namespace("explorer"), - ) - prom.Instrument() - router.Use(db.Middleware) - - router.Path("/metrics").Handler(promhttp.Handler()).Name("metrics") - router.PathPrefix("/public/").Handler(http.StripPrefix("/public/", http.FileServer(statikFS))) - router.Path("/").HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - r, err := statikFS.Open("/index.html") - if err != nil { - mw.Error(err, http.StatusInternalServerError) - return - } - defer r.Close() - - w.WriteHeader(http.StatusOK) - if _, err := io.Copy(w, r); err != nil { - log.Error().Err(err).Send() - } - }) - - if dropEscrowData { - log.Warn().Msg("dropping escrow and address collection") - if err := db.Database().Collection(escrowdb.AddressCollection).Drop(context.Background()); err != nil { - log.Fatal().Err(err).Msg("failed to drop address collection") - } - if err := db.Database().Collection(escrowdb.EscrowCollection).Drop(context.Background()); err != nil { - log.Fatal().Err(err).Msg("failed to drop escrow collection") - } - log.Info().Msg("escrow and address collection dropped successfully. restart the explorer without \"flush-escrows\" flag") - os.Exit(0) - } - - var e escrow.Escrow - if seed != "" { - log.Info().Msgf("escrow enabled on %s", config.Config.Network) - if err := escrowdb.Setup(context.Background(), db.Database()); err != nil { - log.Fatal().Err(err).Msg("failed to create escrow database indexes") - } - - wallet, err := stellar.New(seed, config.Config.Network, backupSigners) - if err != nil { - log.Fatal().Err(err).Msg("failed to create stellar wallet") - } - - e = escrow.NewStellar(wallet, db.Database(), foundationAddress) - - } else { - log.Info().Msg("escrow disabled") - e = escrow.NewFree(db.Database()) - } - - go e.Run(context.Background()) - - pkgs := []Pkg{ - phonebook.Setup, - directory.Setup, - } - - apiRouter := router.PathPrefix("/explorer").Subrouter() - for _, pkg := range pkgs { - if err := pkg(apiRouter, db.Database()); err != nil { - log.Error().Err(err).Msg("failed to register package") - } - } - - if err = workloads.Setup(apiRouter, db.Database(), e); err != nil { - log.Error().Err(err).Msg("failed to register package") - } - - log.Printf("start on %s\n", listen) - r := handlers.LoggingHandler(os.Stderr, router) - r = handlers.CORS(handlers.AllowedOrigins([]string{"*"}))(r) - - return &http.Server{ - Addr: listen, - Handler: r, - }, nil -} - -func userInputYesNo(question string) bool { - var reply string - fmt.Printf("%s (yes/no): ", question) - _, err := fmt.Scan(&reply) - if err != nil { - // if we can't read from the cmd line something is really really wrong - log.Fatal().Err(err).Msg("could not read user reply from command line") - } - - reply = strings.TrimSpace(reply) - reply = strings.ToLower(reply) - - return reply == "y" || reply == "yes" -} diff --git a/tools/explorer/migrations/container_missing_fields/main.go b/tools/explorer/migrations/container_missing_fields/main.go deleted file mode 100644 index 115b4be6e..000000000 --- a/tools/explorer/migrations/container_missing_fields/main.go +++ /dev/null @@ -1,110 +0,0 @@ -// This script is used to update the missing field from the ContainerCapacity type -// It fields the empty value with the value from the JSON field on the reservation type - -package main - -import ( - "context" - "encoding/json" - "flag" - "reflect" - - generated "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" - - "github.com/rs/zerolog/log" - - "github.com/threefoldtech/zos/pkg/app" - "github.com/threefoldtech/zos/tools/explorer/pkg/workloads/types" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -func connectDB(ctx context.Context, connectionURI string) (*mongo.Client, error) { - client, err := mongo.NewClient(options.Client().ApplyURI(connectionURI)) - if err != nil { - return nil, err - } - - if err := client.Connect(ctx); err != nil { - return nil, err - } - - return client, nil -} - -func main() { - app.Initialize() - - var ( - dbConf string - name string - ) - - flag.StringVar(&dbConf, "mongo", "mongodb://localhost:27017", "connection string to mongo database") - flag.StringVar(&name, "name", "explorer", "database name") - flag.Parse() - - ctx := context.TODO() - - client, err := connectDB(ctx, dbConf) - if err != nil { - log.Fatal().Err(err).Msg("failed to connect to database") - } - defer client.Disconnect(ctx) - - db := client.Database(name, nil) - col := db.Collection(types.ReservationCollection) - - cur, err := col.Find(ctx, bson.D{}) - if err != nil { - log.Fatal().Err(err).Send() - } - defer cur.Close(ctx) - - for cur.Next(ctx) { - r := types.Reservation{} - if err := cur.Decode(&r); err != nil { - log.Error().Err(err).Send() - continue - } - - var data generated.ReservationData - - if err := json.Unmarshal([]byte(r.Json), &data); err != nil { - log.Fatal().Err(err).Msg("invalid json data on reservation") - } - - if !reflect.DeepEqual(r.DataReservation, data) { - log.Info().Msgf("start update %d", r.ID) - - for i := range r.DataReservation.Containers { - r.DataReservation.Containers[i].Capacity.Cpu = data.Containers[i].Capacity.Cpu - r.DataReservation.Containers[i].Capacity.Memory = data.Containers[i].Capacity.Memory - for y := range r.DataReservation.Containers[i].NetworkConnection { - r.DataReservation.Containers[i].NetworkConnection[y].PublicIp6 = data.Containers[i].NetworkConnection[y].PublicIp6 - } - } - - if !reflect.DeepEqual(r.DataReservation, data) { - log.Error().Msgf("\n%+v\n%+v", r.DataReservation, data) - log.Fatal().Msg("json data does not match the reservation data") - } - - filter := bson.D{} - filter = append(filter, bson.E{Key: "_id", Value: r.ID}) - - res, err := col.UpdateOne(ctx, filter, bson.M{"$set": r}) - if err != nil { - log.Fatal().Err(err).Msgf("failed to update %d", r.ID) - } - if res.ModifiedCount == 1 { - log.Info().Msgf("updated %d", r.ID) - } else { - log.Error().Msgf("no document updated %d", r.ID) - } - - } - - } -} diff --git a/tools/explorer/migrations/migration/convert.py b/tools/explorer/migrations/migration/convert.py deleted file mode 100644 index 9389063fd..000000000 --- a/tools/explorer/migrations/migration/convert.py +++ /dev/null @@ -1,28 +0,0 @@ -from yaml import load, CLoader -import json -import sys -import os - - -def main(args): - if len(args) == 0: - raise Exception("required file name") - name = args[0] - input = open(name) - - data = load(input, CLoader) - input.close() - - output_name = "%s.json" % name - output = open(output_name, 'w') - try: - json.dump(data, output) - except Exception as e: - os.remove(output_name) - raise e - finally: - output.close() - - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/tools/explorer/migrations/migration/migration.go b/tools/explorer/migrations/migration/migration.go deleted file mode 100644 index 92e2f4dbf..000000000 --- a/tools/explorer/migrations/migration/migration.go +++ /dev/null @@ -1,192 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "flag" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - "github.com/threefoldtech/zos/pkg/app" - "github.com/threefoldtech/zos/tools/explorer/mw" - - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" - - directory "github.com/threefoldtech/zos/tools/explorer/pkg/directory/types" - phonebook "github.com/threefoldtech/zos/tools/explorer/pkg/phonebook/types" - workloads "github.com/threefoldtech/zos/tools/explorer/pkg/workloads/types" -) - -func foreach(root string, f func(p string, r io.Reader) error) error { - files, err := ioutil.ReadDir(root) - if err != nil { - return err - } - - for _, file := range files { - if file.IsDir() { - continue - } - - if filepath.Ext(file.Name()) != ".json" { - continue - } - - p := filepath.Join(root, file.Name()) - fd, err := os.Open(p) - if err != nil { - return err - } - - if err := f(p, fd); err != nil { - fd.Close() - return err - } - - fd.Close() - } - - return nil -} - -// Migrator callback -type Migrator func(root string, db *mongo.Database) error - -func migrateFarms(root string, db *mongo.Database) error { - col := db.Collection(directory.FarmCollection) - return foreach(root, func(p string, r io.Reader) error { - var farm directory.Farm - if err := json.NewDecoder(r).Decode(&farm); err != nil { - return errors.Wrapf(err, "failed to load file '%s'", p) - } - - _, err := col.InsertOne(context.TODO(), farm) - if err != nil { - log.Error().Err(err).Msgf("failed to insert farm '%s'", p) - } - - return nil - }) -} - -func migrateNodes(root string, db *mongo.Database) error { - col := db.Collection(directory.NodeCollection) - return foreach(root, func(p string, r io.Reader) error { - var node directory.Node - if err := json.NewDecoder(r).Decode(&node); err != nil { - return errors.Wrapf(err, "failed to load file '%s'", p) - } - - if err := node.Validate(); err != nil { - return errors.Wrapf(err, "file '%s'", p) - } - - _, err := col.InsertOne(context.TODO(), node) - if err != nil { - log.Error().Err(err).Msgf("failed to insert node '%s'", p) - } - - return nil - }) -} - -func migrateUsers(root string, db *mongo.Database) error { - col := db.Collection(phonebook.UserCollection) - return foreach(root, func(p string, r io.Reader) error { - var user phonebook.User - if err := json.NewDecoder(r).Decode(&user); err != nil { - return errors.Wrapf(err, "failed to load file '%s'", p) - } - - _, err := col.InsertOne(context.TODO(), user) - if err != nil { - log.Error().Err(err).Msgf("failed to insert user '%s'", p) - } - - return nil - }) -} - -func migrateReservations(root string, db *mongo.Database) error { - col := db.Collection(workloads.ReservationCollection) - return foreach(root, func(p string, r io.Reader) error { - var reservation workloads.Reservation - if err := json.NewDecoder(r).Decode(&reservation); err != nil { - return errors.Wrapf(err, "failed to load file '%s'", p) - } - - _, err := col.InsertOne(context.TODO(), reservation) - if err != nil { - log.Error().Err(err).Msgf("failed to insert reservation '%s'", p) - } - - return nil - }) -} - -func connectDB(ctx context.Context, connectionURI string) (*mongo.Client, error) { - client, err := mongo.NewClient(options.Client().ApplyURI(connectionURI)) - if err != nil { - return nil, err - } - - if err := client.Connect(ctx); err != nil { - return nil, err - } - - return client, nil -} - -func main() { - app.Initialize() - - var ( - root string - dbConf string - name string - ) - - flag.StringVar(&dbConf, "mongo", "mongodb://localhost:27017", "connection string to mongo database") - flag.StringVar(&name, "name", "explorer", "database name") - flag.StringVar(&root, "root", "", "root directory of the bcdb exported data") - - flag.Parse() - if strings.EqualFold(root, "") { - log.Fatal().Msg("root option is required") - } - - ctx := context.TODO() - - db, err := connectDB(ctx, dbConf) - if err != nil { - log.Fatal().Err(err).Msg("failed to connect to database") - } - - dbMw, err := mw.NewDatabaseMiddleware(name, db) - if err != nil { - log.Fatal().Err(err).Msg("failed to connect to database") - } - - if err := directory.Setup(ctx, dbMw.Database()); err != nil { - log.Fatal().Err(err).Msg("failed to setup directory indexes") - } - - types := map[string]Migrator{ - "tfgrid_directory/tfgrid.directory.farm.1/yaml": migrateFarms, - "tfgrid_directory/tfgrid.directory.node.2/yaml": migrateNodes, - "phonebook/tfgrid.phonebook.user.1/yaml": migrateUsers, - "tfgrid_workloads/tfgrid.workloads.reservation.1/yaml": migrateReservations, - } - - for typ, migrator := range types { - if err := migrator(filepath.Join(root, typ), dbMw.Database()); err != nil { - log.Error().Err(err).Str("root", typ).Msg("migration failed") - } - } -} diff --git a/tools/explorer/migrations/migration/readme.me b/tools/explorer/migrations/migration/readme.me deleted file mode 100644 index f0a6dc529..000000000 --- a/tools/explorer/migrations/migration/readme.me +++ /dev/null @@ -1,15 +0,0 @@ -## Migration -this migration tool accepts the path to a bcdb exported data directory and iterate over all structures that we need -to import in the database. - -> *NOTE:* You need to convert all the data from yaml to json data. We don't support importing the yaml data. to make this easy -please use the `convert.py` util as follows - -```bash -find ~/bcdb_exports/18_Mar_2020_11_48_38/ -name '*.yaml' -print -exec python convert.py {} \; -``` - -Then run migration like -```bash -./migration -name explorer -root ~/tmp/bcdb_exports/18_Mar_2020_11_48_38 -``` diff --git a/tools/explorer/migrations/user_ipaddresss/main.go b/tools/explorer/migrations/user_ipaddresss/main.go deleted file mode 100644 index f3958df24..000000000 --- a/tools/explorer/migrations/user_ipaddresss/main.go +++ /dev/null @@ -1,97 +0,0 @@ -// This script is used to update the field from the User type from Ipaddr net.IP to Host string - -package main - -import ( - "context" - "flag" - "net" - - "github.com/rs/zerolog/log" - - "github.com/threefoldtech/zos/pkg/app" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/pkg/phonebook/types" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -type oldType struct { - ID schema.ID `bson:"_id" json:"id"` - Name string `bson:"name" json:"name"` - Email string `bson:"email" json:"email"` - Pubkey string `bson:"pubkey" json:"pubkey"` - Ipaddr net.IP `bson:"ipaddr" json:"ipaddr"` - Description string `bson:"description" json:"description"` - Signature string `bson:"signature" json:"signature"` -} - -func connectDB(ctx context.Context, connectionURI string) (*mongo.Client, error) { - client, err := mongo.NewClient(options.Client().ApplyURI(connectionURI)) - if err != nil { - return nil, err - } - - if err := client.Connect(ctx); err != nil { - return nil, err - } - - return client, nil -} - -func main() { - app.Initialize() - - var ( - dbConf string - name string - ) - - flag.StringVar(&dbConf, "mongo", "mongodb://localhost:27017", "connection string to mongo database") - flag.StringVar(&name, "name", "explorer", "database name") - flag.Parse() - - ctx := context.TODO() - - client, err := connectDB(ctx, dbConf) - if err != nil { - log.Fatal().Err(err).Msg("failed to connect to database") - } - defer client.Disconnect(ctx) - - db := client.Database(name, nil) - col := db.Collection(types.UserCollection) - - cur, err := col.Find(ctx, bson.D{}) - if err != nil { - log.Fatal().Err(err).Send() - } - defer cur.Close(ctx) - - for cur.Next(ctx) { - old := oldType{} - if err := cur.Decode(&old); err != nil { - log.Fatal().Err(err).Send() - } - - log.Info().Msgf("start update %d %s", old.ID, old.Name) - - new := types.User{ - ID: old.ID, - Name: old.Name, - Email: old.Email, - Pubkey: old.Pubkey, - Host: old.Ipaddr.String(), - Description: old.Description, - Signature: old.Signature, - } - - f := types.UserFilter{} - f = f.WithID(old.ID) - if _, err := col.UpdateOne(ctx, f, bson.M{"$set": new}); err != nil { - log.Fatal().Err(err).Msgf("failed to update %d", old.ID) - } - log.Info().Msgf("updated %d %s", old.ID, old.Name) - } -} diff --git a/tools/explorer/migrations/wallet_address/main.go b/tools/explorer/migrations/wallet_address/main.go deleted file mode 100644 index e42ef98d7..000000000 --- a/tools/explorer/migrations/wallet_address/main.go +++ /dev/null @@ -1,107 +0,0 @@ -// This script is used to update the WalletAddresses field from the Farm type - -package main - -import ( - "context" - "flag" - - "github.com/rs/zerolog/log" - - "github.com/threefoldtech/zos/pkg/app" - "github.com/threefoldtech/zos/pkg/schema" - generated "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" - "github.com/threefoldtech/zos/tools/explorer/pkg/directory/types" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -type oldType struct { - ID schema.ID `bson:"_id" json:"id"` - ThreebotId int64 `bson:"threebot_id" json:"threebot_id"` - IyoOrganization string `bson:"iyo_organization" json:"iyo_organization"` - Name string `bson:"name" json:"name"` - WalletAddresses []string `bson:"wallet_addresses" json:"wallet_addresses"` - Location generated.Location `bson:"location" json:"location"` - Email schema.Email `bson:"email" json:"email"` - ResourcePrices []generated.NodeResourcePrice `bson:"resource_prices" json:"resource_prices"` - PrefixZero schema.IPRange `bson:"prefix_zero" json:"prefix_zero"` -} - -func connectDB(ctx context.Context, connectionURI string) (*mongo.Client, error) { - client, err := mongo.NewClient(options.Client().ApplyURI(connectionURI)) - if err != nil { - return nil, err - } - - if err := client.Connect(ctx); err != nil { - return nil, err - } - - return client, nil -} - -func main() { - app.Initialize() - - var ( - dbConf string - name string - ) - - flag.StringVar(&dbConf, "mongo", "mongodb://localhost:27017", "connection string to mongo database") - flag.StringVar(&name, "name", "explorer", "database name") - flag.Parse() - - ctx := context.TODO() - - client, err := connectDB(ctx, dbConf) - if err != nil { - log.Fatal().Err(err).Msg("failed to connect to database") - } - defer client.Disconnect(ctx) - - db := client.Database(name, nil) - col := db.Collection(types.FarmCollection) - - cur, err := col.Find(ctx, bson.D{}) - if err != nil { - log.Fatal().Err(err).Send() - } - defer cur.Close(ctx) - - for cur.Next(ctx) { - old := oldType{} - if err := cur.Decode(&old); err != nil { - log.Fatal().Err(err).Send() - } - - log.Info().Msgf("start update %d %s", old.ID, old.Name) - - new := types.Farm{ - ID: old.ID, - ThreebotId: old.ThreebotId, - IyoOrganization: old.IyoOrganization, - Name: old.Name, - WalletAddresses: make([]generated.WalletAddress, len(old.WalletAddresses)), - Location: old.Location, - Email: old.Email, - ResourcePrices: old.ResourcePrices, - PrefixZero: old.PrefixZero, - } - for i := range old.WalletAddresses { - new.WalletAddresses[i] = generated.WalletAddress{ - Asset: "TFChain", - Address: old.WalletAddresses[i], - } - } - - f := types.FarmFilter{} - f = f.WithID(old.ID) - if _, err := col.UpdateOne(ctx, f, bson.M{"$set": new}); err != nil { - log.Fatal().Err(err).Msgf("failed to update %d", old.ID) - } - log.Info().Msgf("updated %d %s", old.ID, old.Name) - } -} diff --git a/tools/explorer/models/generate.go b/tools/explorer/models/generate.go deleted file mode 100644 index d9dc263a0..000000000 --- a/tools/explorer/models/generate.go +++ /dev/null @@ -1,9 +0,0 @@ -package models - -//go:generate rm -rf generated -//go:generate mkdir -p generated/directory -//go:generate mkdir -p generated/workloads -//go:generate mkdir -p generated/phonebook -//go:generate schemac -pkg directory -dir generated/directory -in schema/directory -//go:generate schemac -pkg workloads -dir generated/workloads -in schema/workloads -//go:generate schemac -pkg phonebook -dir generated/phonebook -in schema/phonebook diff --git a/tools/explorer/models/generated/directory/directory.go b/tools/explorer/models/generated/directory/directory.go deleted file mode 100644 index fb8ff9b56..000000000 --- a/tools/explorer/models/generated/directory/directory.go +++ /dev/null @@ -1,231 +0,0 @@ -package directory - -import ( - "encoding/json" - "net" - - schema "github.com/threefoldtech/zos/pkg/schema" -) - -type Farm struct { - ID schema.ID `bson:"_id" json:"id"` - ThreebotId int64 `bson:"threebot_id" json:"threebot_id"` - IyoOrganization string `bson:"iyo_organization" json:"iyo_organization"` - Name string `bson:"name" json:"name"` - WalletAddresses []WalletAddress `bson:"wallet_addresses" json:"wallet_addresses"` - Location Location `bson:"location" json:"location"` - Email schema.Email `bson:"email" json:"email"` - ResourcePrices []NodeResourcePrice `bson:"resource_prices" json:"resource_prices"` - PrefixZero schema.IPRange `bson:"prefix_zero" json:"prefix_zero"` -} - -func NewFarm() (Farm, error) { - const value = "{}" - var object Farm - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type WalletAddress struct { - Asset string `bson:"asset" json:"asset"` - Address string `bson:"address" json:"address"` -} - -type NodeResourcePrice struct { - Currency PriceCurrencyEnum `bson:"currency" json:"currency"` - Cru float64 `bson:"cru" json:"cru"` - Mru float64 `bson:"mru" json:"mru"` - Hru float64 `bson:"hru" json:"hru"` - Sru float64 `bson:"sru" json:"sru"` - Nru float64 `bson:"nru" json:"nru"` -} - -func NewNodeResourcePrice() (NodeResourcePrice, error) { - const value = "{}" - var object NodeResourcePrice - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type Location struct { - City string `bson:"city" json:"city"` - Country string `bson:"country" json:"country"` - Continent string `bson:"continent" json:"continent"` - Latitude float64 `bson:"latitude" json:"latitude"` - Longitude float64 `bson:"longitude" json:"longitude"` -} - -func NewLocation() (Location, error) { - const value = "{}" - var object Location - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type Node struct { - ID schema.ID `bson:"_id" json:"id"` - NodeId string `bson:"node_id" json:"node_id"` - NodeIdV1 string `bson:"node_id_v1" json:"node_id_v1"` - FarmId int64 `bson:"farm_id" json:"farm_id"` - OsVersion string `bson:"os_version" json:"os_version"` - Created schema.Date `bson:"created" json:"created"` - Updated schema.Date `bson:"updated" json:"updated"` - Uptime int64 `bson:"uptime" json:"uptime"` - Address string `bson:"address" json:"address"` - Location Location `bson:"location" json:"location"` - TotalResources ResourceAmount `bson:"total_resources" json:"total_resources"` - UsedResources ResourceAmount `bson:"used_resources" json:"used_resources"` - ReservedResources ResourceAmount `bson:"reserved_resources" json:"reserved_resources"` - Workloads WorkloadAmount `bson:"workloads" json:"workloads"` - Proofs []Proof `bson:"proofs" json:"proofs"` - Ifaces []Iface `bson:"ifaces" json:"ifaces"` - PublicConfig *PublicIface `bson:"public_config,omitempty" json:"public_config"` - FreeToUse bool `bson:"free_to_use" json:"free_to_use"` - Approved bool `bson:"approved" json:"approved"` - PublicKeyHex string `bson:"public_key_hex" json:"public_key_hex"` - WgPorts []int64 `bson:"wg_ports" json:"wg_ports"` -} - -func NewNode() (Node, error) { - const value = "{\"approved\": false, \"public_key_hex\": \"\"}" - var object Node - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type Iface struct { - Name string `bson:"name" json:"name"` - Addrs []schema.IPRange `bson:"addrs" json:"addrs"` - Gateway []net.IP `bson:"gateway" json:"gateway"` - MacAddress schema.MacAddress `bson:"macaddress" json:"macaddress"` -} - -func NewIface() (Iface, error) { - const value = "{}" - var object Iface - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type PublicIface struct { - Master string `bson:"master" json:"master"` - Type IfaceTypeEnum `bson:"type" json:"type"` - Ipv4 schema.IPRange `bson:"ipv4" json:"ipv4"` - Ipv6 schema.IPRange `bson:"ipv6" json:"ipv6"` - Gw4 net.IP `bson:"gw4" json:"gw4"` - Gw6 net.IP `bson:"gw6" json:"gw6"` - Version int64 `bson:"version" json:"version"` -} - -func NewPublicIface() (PublicIface, error) { - const value = "{}" - var object PublicIface - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type ResourceAmount struct { - Cru uint64 `bson:"cru" json:"cru"` - Mru float64 `bson:"mru" json:"mru"` - Hru float64 `bson:"hru" json:"hru"` - Sru float64 `bson:"sru" json:"sru"` -} - -func NewResourceAmount() (ResourceAmount, error) { - const value = "{}" - var object ResourceAmount - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type WorkloadAmount struct { - Network uint16 `bson:"network" json:"network"` - Volume uint16 `bson:"volume" json:"volume"` - ZDBNamespace uint16 `bson:"zdb_namespace" json:"zdb_namespace"` - Container uint16 `bson:"container" json:"container"` - K8sVM uint16 `bson:"k8s_vm" json:"k8s_vm"` -} - -func NewWorkloadAmount() (WorkloadAmount, error) { - const value = "{}" - var object WorkloadAmount - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type Proof struct { - Created schema.Date `bson:"created" json:"created"` - HardwareHash string `bson:"hardware_hash" json:"hardware_hash"` - DiskHash string `bson:"disk_hash" json:"disk_hash"` - Hardware map[string]interface{} `bson:"hardware" json:"hardware"` - Disks map[string]interface{} `bson:"disks" json:"disks"` - Hypervisor []string `bson:"hypervisor" json:"hypervisor"` -} - -func NewProof() (Proof, error) { - const value = "{}" - var object Proof - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type IfaceTypeEnum uint8 - -const ( - IfaceTypeMacvlan IfaceTypeEnum = iota - IfaceTypeVlan -) - -func (e IfaceTypeEnum) String() string { - switch e { - case IfaceTypeMacvlan: - return "macvlan" - case IfaceTypeVlan: - return "vlan" - } - return "UNKNOWN" -} - -type PriceCurrencyEnum uint8 - -const ( - PriceCurrencyEUR PriceCurrencyEnum = iota - PriceCurrencyUSD - PriceCurrencyTFT - PriceCurrencyAED - PriceCurrencyGBP -) - -func (e PriceCurrencyEnum) String() string { - switch e { - case PriceCurrencyEUR: - return "EUR" - case PriceCurrencyUSD: - return "USD" - case PriceCurrencyTFT: - return "TFT" - case PriceCurrencyAED: - return "AED" - case PriceCurrencyGBP: - return "GBP" - } - return "UNKNOWN" -} diff --git a/tools/explorer/models/generated/phonebook/phonebook.go b/tools/explorer/models/generated/phonebook/phonebook.go deleted file mode 100644 index 8a130e3ce..000000000 --- a/tools/explorer/models/generated/phonebook/phonebook.go +++ /dev/null @@ -1,26 +0,0 @@ -package phonebook - -import ( - "encoding/json" - - schema "github.com/threefoldtech/zos/pkg/schema" -) - -type User struct { - ID schema.ID `bson:"_id" json:"id"` - Name string `bson:"name" json:"name"` - Email string `bson:"email" json:"email"` - Pubkey string `bson:"pubkey" json:"pubkey"` - Host string `bson:"host" json:"host"` - Description string `bson:"description" json:"description"` - Signature string `bson:"signature" json:"signature"` -} - -func NewUser() (User, error) { - const value = "{}" - var object User - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} diff --git a/tools/explorer/models/generated/workloads/workloads.go b/tools/explorer/models/generated/workloads/workloads.go deleted file mode 100644 index ca73fd781..000000000 --- a/tools/explorer/models/generated/workloads/workloads.go +++ /dev/null @@ -1,481 +0,0 @@ -package workloads - -import ( - "encoding/json" - "net" - - schema "github.com/threefoldtech/zos/pkg/schema" -) - -type Reservation struct { - ID schema.ID `bson:"_id" json:"id"` - Json string `bson:"json" json:"json"` - DataReservation ReservationData `bson:"data_reservation" json:"data_reservation"` - CustomerTid int64 `bson:"customer_tid" json:"customer_tid"` - CustomerSignature string `bson:"customer_signature" json:"customer_signature"` - NextAction NextActionEnum `bson:"next_action" json:"next_action"` - SignaturesProvision []SigningSignature `bson:"signatures_provision" json:"signatures_provision"` - SignaturesFarmer []SigningSignature `bson:"signatures_farmer" json:"signatures_farmer"` - SignaturesDelete []SigningSignature `bson:"signatures_delete" json:"signatures_delete"` - Epoch schema.Date `bson:"epoch" json:"epoch"` - Results []Result `bson:"results" json:"results"` -} - -func NewReservation() (Reservation, error) { - const value = "{\"json\": \"\"}" - var object Reservation - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type ReservationData struct { - Description string `bson:"description" json:"description"` - Currencies []string `bson:"currencies" json:"currencies"` - SigningRequestProvision SigningRequest `bson:"signing_request_provision" json:"signing_request_provision"` - SigningRequestDelete SigningRequest `bson:"signing_request_delete" json:"signing_request_delete"` - Containers []Container `bson:"containers" json:"containers"` - Volumes []Volume `bson:"volumes" json:"volumes"` - Zdbs []ZDB `bson:"zdbs" json:"zdbs"` - Networks []Network `bson:"networks" json:"networks"` - Kubernetes []K8S `bson:"kubernetes" json:"kubernetes"` - ExpirationProvisioning schema.Date `bson:"expiration_provisioning" json:"expiration_provisioning"` - ExpirationReservation schema.Date `bson:"expiration_reservation" json:"expiration_reservation"` -} - -func NewReservationData() (ReservationData, error) { - const value = "{\"description\": \"\"}" - var object ReservationData - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type SigningRequest struct { - Signers []int64 `bson:"signers" json:"signers"` - QuorumMin int64 `bson:"quorum_min" json:"quorum_min"` -} - -func NewSigningRequest() (SigningRequest, error) { - const value = "{}" - var object SigningRequest - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type SigningSignature struct { - Tid int64 `bson:"tid" json:"tid"` - Signature string `bson:"signature" json:"signature"` - Epoch schema.Date `bson:"epoch" json:"epoch"` -} - -func NewSigningSignature() (SigningSignature, error) { - const value = "{}" - var object SigningSignature - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type Container struct { - WorkloadId int64 `bson:"workload_id" json:"workload_id"` - NodeId string `bson:"node_id" json:"node_id"` - Flist string `bson:"flist" json:"flist"` - HubUrl string `bson:"hub_url" json:"hub_url"` - Environment map[string]string `bson:"environment" json:"environment"` - SecretEnvironment map[string]string `bson:"secret_environment" json:"secret_environment"` - Entrypoint string `bson:"entrypoint" json:"entrypoint"` - Interactive bool `bson:"interactive" json:"interactive"` - Volumes []ContainerMount `bson:"volumes" json:"volumes"` - NetworkConnection []NetworkConnection `bson:"network_connection" json:"network_connection"` - StatsAggregator []StatsAggregator `bson:"stats_aggregator" json:"stats_aggregator"` - Logs []Logs `bson:"logs" json:"logs"` - FarmerTid int64 `bson:"farmer_tid" json:"farmer_tid"` - Capacity ContainerCapacity `bson:"capcity" json:"capacity"` -} - -type ContainerCapacity struct { - Cpu int64 `bson:"cpu" json:"cpu"` - Memory int64 `bson:"memory" json:"memory"` -} - -type Logs struct { - Type string `bson:"type" json:"type"` - Data LogsRedis `bson:"data" json:"data"` -} - -type LogsRedis struct { - Stdout string `bson:"stdout" json:"stdout"` - Stderr string `bson:"stderr" json:"stderr"` -} - -func NewContainer() (Container, error) { - const value = "{\"interactive\": true}" - var object Container - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type ContainerMount struct { - VolumeId string `bson:"volume_id" json:"volume_id"` - Mountpoint string `bson:"mountpoint" json:"mountpoint"` -} - -func NewTfgridWorkloadsReservationContainerMount1() (ContainerMount, error) { - const value = "{}" - var object ContainerMount - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type NetworkConnection struct { - NetworkId string `bson:"network_id" json:"network_id"` - Ipaddress net.IP `bson:"ipaddress" json:"ipaddress"` - PublicIp6 bool `bson:"public_ip6" json:"public_ip6"` -} - -func NewNetworkConnection() (NetworkConnection, error) { - const value = "{}" - var object NetworkConnection - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type K8S struct { - WorkloadId int64 `bson:"workload_id" json:"workload_id"` - NodeId string `bson:"node_id" json:"node_id"` - Size int64 `bson:"size" json:"size"` - NetworkId string `bson:"network_id" json:"network_id"` - Ipaddress net.IP `bson:"ipaddress" json:"ipaddress"` - ClusterSecret string `bson:"cluster_secret" json:"cluster_secret"` - MasterIps []net.IP `bson:"master_ips" json:"master_ips"` - SshKeys []string `bson:"ssh_keys" json:"ssh_keys"` - StatsAggregator []StatsAggregator `bson:"stats_aggregator" json:"stats_aggregator"` - FarmerTid int64 `bson:"farmer_tid" json:"farmer_tid"` -} - -func NewK8S() (K8S, error) { - const value = "{}" - var object K8S - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type Network struct { - Name string `bson:"name" json:"name"` - WorkloadId int64 `bson:"workload_id" json:"workload_id"` - Iprange schema.IPRange `bson:"iprange" json:"iprange"` - StatsAggregator []StatsAggregator `bson:"stats_aggregator" json:"stats_aggregator"` - NetworkResources []NetworkNetResource `bson:"network_resources" json:"network_resources"` - FarmerTid int64 `bson:"farmer_tid" json:"farmer_tid"` -} - -func NewNetwork() (Network, error) { - const value = "{\"name\": \"\", \"iprange\": \"10.10.0.0/16\"}" - var object Network - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type NetworkNetResource struct { - NodeId string `bson:"node_id" json:"node_id"` - WireguardPrivateKeyEncrypted string `bson:"wireguard_private_key_encrypted" json:"wireguard_private_key_encrypted"` - WireguardPublicKey string `bson:"wireguard_public_key" json:"wireguard_public_key"` - WireguardListenPort int64 `bson:"wireguard_listen_port" json:"wireguard_listen_port"` - Iprange schema.IPRange `bson:"iprange" json:"iprange"` - Peers []WireguardPeer `bson:"peers" json:"peers"` -} - -func NewNetworkNetResource() (NetworkNetResource, error) { - const value = "{\"wireguard_private_key_encrypted\": \"\", \"wireguard_public_key\": \"\", \"iprange\": \"10.10.10.0/24\"}" - var object NetworkNetResource - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type WireguardPeer struct { - PublicKey string `bson:"public_key" json:"public_key"` - AllowedIprange []schema.IPRange `bson:"allowed_iprange" json:"allowed_iprange"` - Endpoint string `bson:"endpoint" json:"endpoint"` - Iprange schema.IPRange `bson:"iprange" json:"iprange"` -} - -func NewPeer() (WireguardPeer, error) { - const value = "{\"public_key\": \"\", \"allowed_iprange\": [], \"endpoint\": \"\", \"iprange\": \"10.10.11.0/24\"}" - var object WireguardPeer - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type Result struct { - Category ResultCategoryEnum `bson:"category" json:"category"` - WorkloadId string `bson:"workload_id" json:"workload_id"` - DataJson json.RawMessage `bson:"data_json" json:"data_json"` - Signature string `bson:"signature" json:"signature"` - State ResultStateEnum `bson:"state" json:"state"` - Message string `bson:"message" json:"message"` - Epoch schema.Date `bson:"epoch" json:"epoch"` - NodeId string `bson:"node_id" json:"node_id"` -} - -func NewResult() (Result, error) { - const value = "{\"message\": \"\"}" - var object Result - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type StatsAggregator struct { - Type string `bson:"type" json:"type"` - Data StatsRedis `bson:"data" json:"data"` -} - -type StatsRedis struct { - Endpoint string `bson:"stdout" json:"endpoint"` -} - -func NewStatsAggregator() (StatsAggregator, error) { - const value = "{}" - var object StatsAggregator - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type Volume struct { - WorkloadId int64 `bson:"workload_id" json:"workload_id"` - NodeId string `bson:"node_id" json:"node_id"` - Size int64 `bson:"size" json:"size"` - Type VolumeTypeEnum `bson:"type" json:"type"` - StatsAggregator []StatsAggregator `bson:"stats_aggregator" json:"stats_aggregator"` - FarmerTid int64 `bson:"farmer_tid" json:"farmer_tid"` -} - -func NewVolume() (Volume, error) { - const value = "{}" - var object Volume - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -// NOTE: this type has some manual changes -// that need to be preserved between regenerations. -type ReservationWorkload struct { - WorkloadId string `bson:"workload_id" json:"workload_id"` - User string `bson:"user" json:"user"` - Type WorkloadTypeEnum `bson:"type" json:"type"` - Content interface{} `bson:"content" json:"content"` - Created schema.Date `bson:"created" json:"created"` - Duration int64 `bson:"duration" json:"duration"` - Signature string `bson:"signature" json:"signature"` - ToDelete bool `bson:"to_delete" json:"to_delete"` -} - -func NewReservationWorkload() (ReservationWorkload, error) { - const value = "{}" - var object ReservationWorkload - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type ZDB struct { - WorkloadId int64 `bson:"workload_id" json:"workload_id"` - NodeId string `bson:"node_id" json:"node_id"` - Size int64 `bson:"size" json:"size"` - Mode ZDBModeEnum `bson:"mode" json:"mode"` - Password string `bson:"password" json:"password"` - DiskType DiskTypeEnum `bson:"disk_type" json:"disk_type"` - Public bool `bson:"public" json:"public"` - StatsAggregator []StatsAggregator `bson:"stats_aggregator" json:"stats_aggregator"` - FarmerTid int64 `bson:"farmer_tid" json:"farmer_tid"` -} - -func NewZDB() (ZDB, error) { - const value = "{\"public\": false}" - var object ZDB - if err := json.Unmarshal([]byte(value), &object); err != nil { - return object, err - } - return object, nil -} - -type DiskTypeEnum uint8 - -const ( - DiskTypeHDD DiskTypeEnum = iota - DiskTypeSSD -) - -func (e DiskTypeEnum) String() string { - switch e { - case DiskTypeHDD: - return "hdd" - case DiskTypeSSD: - return "ssd" - } - return "UNKNOWN" -} - -type NextActionEnum uint8 - -const ( - NextActionCreate NextActionEnum = iota - NextActionSign - NextActionPay - NextActionDeploy - NextActionDelete - NextActionInvalid - NextActionDeleted -) - -func (e NextActionEnum) String() string { - switch e { - case NextActionCreate: - return "create" - case NextActionSign: - return "sign" - case NextActionPay: - return "pay" - case NextActionDeploy: - return "deploy" - case NextActionDelete: - return "delete" - case NextActionInvalid: - return "invalid" - case NextActionDeleted: - return "deleted" - } - return "UNKNOWN" -} - -type ResultCategoryEnum uint8 - -const ( - ResultCategoryZDB ResultCategoryEnum = iota - ResultCategoryContainer - ResultCategoryNetwork - ResultCategoryVolume - ResultCategoryK8S -) - -func (e ResultCategoryEnum) String() string { - switch e { - case ResultCategoryZDB: - return "zdb" - case ResultCategoryContainer: - return "container" - case ResultCategoryNetwork: - return "network" - case ResultCategoryVolume: - return "volume" - case ResultCategoryK8S: - return "kubernetes" - } - return "UNKNOWN" -} - -type ResultStateEnum uint8 - -const ( - ResultStateError ResultStateEnum = iota - ResultStateOK - ResultStateDeleted -) - -func (e ResultStateEnum) String() string { - switch e { - case ResultStateError: - return "error" - case ResultStateOK: - return "ok" - case ResultStateDeleted: - return "deleted" - } - return "UNKNOWN" -} - -type VolumeTypeEnum uint8 - -const ( - VolumeTypeHDD VolumeTypeEnum = iota - VolumeTypeSSD -) - -func (e VolumeTypeEnum) String() string { - switch e { - case VolumeTypeHDD: - return "HDD" - case VolumeTypeSSD: - return "SSD" - } - return "UNKNOWN" -} - -type WorkloadTypeEnum uint8 - -const ( - WorkloadTypeZDB WorkloadTypeEnum = iota - WorkloadTypeContainer - WorkloadTypeVolume - WorkloadTypeNetwork - WorkloadTypeKubernetes -) - -func (e WorkloadTypeEnum) String() string { - switch e { - case WorkloadTypeZDB: - return "zdb" - case WorkloadTypeContainer: - return "container" - case WorkloadTypeVolume: - return "volume" - case WorkloadTypeNetwork: - return "network" - case WorkloadTypeKubernetes: - return "kubernetes" - } - return "UNKNOWN" -} - -type ZDBModeEnum uint8 - -const ( - ZDBModeSeq ZDBModeEnum = iota - ZDBModeUser -) - -func (e ZDBModeEnum) String() string { - switch e { - case ZDBModeSeq: - return "seq" - case ZDBModeUser: - return "user" - } - return "UNKNOWN" -} diff --git a/tools/explorer/models/id.go b/tools/explorer/models/id.go deleted file mode 100644 index c0a8ca403..000000000 --- a/tools/explorer/models/id.go +++ /dev/null @@ -1,51 +0,0 @@ -package models - -import ( - "context" - "errors" - "fmt" - - "github.com/threefoldtech/zos/pkg/schema" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const ( - //Counters is the counters collection in mongo - Counters = "counters" -) - -var ( - //ErrFailedToGetID is base error for generation failure - ErrFailedToGetID = errors.New("failed to generate new id") -) - -// NextID for a collection -func NextID(ctx context.Context, db *mongo.Database, collection string) (schema.ID, error) { - result := db.Collection(Counters).FindOneAndUpdate( - ctx, - bson.M{"_id": collection}, - bson.M{"$inc": bson.M{"sequence": 1}}, - options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After), - ) - - if result.Err() != nil { - return 0, result.Err() - } - var value struct { - Sequence schema.ID `bson:"sequence"` - } - err := result.Decode(&value) - return value.Sequence, err -} - -//MustID must get next available ID, or panic with an error that has error.Is(err, ErrFailedToGetID) == true -func MustID(ctx context.Context, db *mongo.Database, collection string) schema.ID { - id, err := NextID(ctx, db, collection) - if err != nil { - panic(fmt.Errorf("%w: %s", ErrFailedToGetID, err.Error())) - } - - return id -} diff --git a/tools/explorer/models/readme.md b/tools/explorer/models/readme.md deleted file mode 100644 index 26e4f5534..000000000 --- a/tools/explorer/models/readme.md +++ /dev/null @@ -1,4 +0,0 @@ -#NOTE -Models in this directory are jsx models. We copy them here from jumpscale threebot -just for easier regeneration. So, they need to be always in sync with the one in -jumpscale threebot repo. diff --git a/tools/explorer/models/schema/directory/tfgrid_directory_farm_1.toml b/tools/explorer/models/schema/directory/tfgrid_directory_farm_1.toml deleted file mode 100644 index 972defe6e..000000000 --- a/tools/explorer/models/schema/directory/tfgrid_directory_farm_1.toml +++ /dev/null @@ -1,24 +0,0 @@ -#is the location, 1 specific farm -@url = tfgrid.directory.farm.1 -threebot_id** = (I) -iyo_organization = (S) # for backward compatibility -name** = (S) -wallet_addresses = (LO) !tfgrid.directory.wallet_address.1 -location = (O) !tfgrid.directory.location.1 -email = (email) -resource_prices = (LO) !tfgrid.directory.node.resource.price.1 -# original /48 allocation of the farm -prefix_zero = (iprange) - - -@url = tfgrid.directory.wallet_address.1 -asset = (S) -address = (S) - -@url = tfgrid.directory.node.resource.price.1 -currency = "EUR,USD,TFT,AED,GBP" (E) -cru = (F) -mru = (F) -hru = (F) -sru = (F) -nru = (F) diff --git a/tools/explorer/models/schema/directory/tfgrid_directory_location_1.toml b/tools/explorer/models/schema/directory/tfgrid_directory_location_1.toml deleted file mode 100644 index e04c6ff86..000000000 --- a/tools/explorer/models/schema/directory/tfgrid_directory_location_1.toml +++ /dev/null @@ -1,7 +0,0 @@ - -@url = tfgrid.directory.location.1 -city = (S) -country = (S) -continent = (S) -latitude = (F) -longitude = (F) diff --git a/tools/explorer/models/schema/directory/tfgrid_directory_node_2.toml b/tools/explorer/models/schema/directory/tfgrid_directory_node_2.toml deleted file mode 100644 index 4f5268e08..000000000 --- a/tools/explorer/models/schema/directory/tfgrid_directory_node_2.toml +++ /dev/null @@ -1,64 +0,0 @@ - -@url = tfgrid.directory.node.2 -node_id** = (S) -node_id_v1 = (S) -farm_id** = (I) -os_version** = (S) -#parameters = (dict) -created = (T) -updated = (T) -uptime = (I) -address = (S) -location = (O) !tfgrid.directory.location.1 -total_resources = (O) !tfgrid.directory.node.resource.amount.1 -used_resources = (O) !tfgrid.directory.node.resource.amount.1 -reserved_resources = (O) !tfgrid.directory.node.resource.amount.1 -workloads = (O)!tfgrid.directory.node.resource.workloads.1 -proofs = (LO) !tfgrid.directory.node.proof.1 -ifaces = (LO) !tfgrid.directory.node.iface.1 -public_config = (O)!tfgrid.directory.node.public_iface.1 -free_to_use = (B) -approved = false (B) -public_key_hex = "" (S) #hex representation of public key of the TF node -wg_ports = (LI) - -#following info is not usable for provisioning, its convenience info for the farmer -#e.g. to know which interface names there are -#is only the physical interfaces where a cable is attached (INFO ONLY) -@url = tfgrid.directory.node.iface.1 -name = (S) -addrs = (Liprange) -gateway = (Lipaddr) -macaddress = (S) - -#famer configures this information so we know how to configure the ipaddress -@url = tfgrid.directory.node.public_iface.1 -master = (S) #physical interface name on which the vlan lives -type = "macvlan,vlan" (E) -ipv4 = (iprange) -ipv6 = (iprange) -gw4 = (ipaddr) -gw6 = (ipaddr) -version = (I) - - -@url = tfgrid.directory.node.resource.amount.1 -cru** = (I) -mru** = (I) -hru** = (I) -sru** = (I) - -@url = tfgrid.directory.node.resource.workloads.1 -network = (I) -volume = (I) -zdb_namespace = (I) -container = (I) -k8s_vm = (I) - -@url = tfgrid.directory.node.proof.1 -created = (T) -hardware_hash = (S) -disk_hash = (S) -hardware = (dict) -disks = (dict) -hypervisor = (LS) diff --git a/tools/explorer/models/schema/phonebook/tfgrid_phonebook_user_1.toml b/tools/explorer/models/schema/phonebook/tfgrid_phonebook_user_1.toml deleted file mode 100644 index ed8d113aa..000000000 --- a/tools/explorer/models/schema/phonebook/tfgrid_phonebook_user_1.toml +++ /dev/null @@ -1,8 +0,0 @@ -@url = tfgrid.phonebook.user.1 -@acl = false -name** = (S) #e.g. kristof.ibiza or kristof.3bot -email** = (S) #optional, not required -pubkey = (S) #public key of the 3bot -host = (S) #how to reach the digitalme (3bot) -description = (S) #optional -signature = (S) #proof that content is ok, is on id+name+email+pubkey+ipaddress+description diff --git a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_1.toml b/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_1.toml deleted file mode 100644 index 40119a6c2..000000000 --- a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_1.toml +++ /dev/null @@ -1,50 +0,0 @@ - -@url = tfgrid.workloads.reservation.1 -#json of the reservation.data object, will not change over time -json = "" (S) -data_reservation = (O) !tfgrid.workloads.reservation.data.1 -#id of threebot which pays for it -customer_tid = (I) -#signature with private key of customer of the json, this guarantees that the data did not change -customer_signature = (S) -#state, allows anyone to see what can happen next e.g. sign means waiting for everyone to sign -#delete means its deployed now we need to wait till enough people sign to delete -next_action = "create,sign,pay,deploy,delete,invalid,deleted" (E) -signatures_provision = (LO) !tfgrid.workloads.reservation.signing.signature.1 -signatures_farmer = (LO) !tfgrid.workloads.reservation.signing.signature.1 -signatures_delete = (LO) !tfgrid.workloads.reservation.signing.signature.1 -epoch = (T) -results = (LO) !tfgrid.workloads.reservation.result.1 - -@url = tfgrid.workloads.reservation.data.1 -#this one does not change over time -description = "" (S) -#list of acceptable currencies for this reservation -currencies = (LS) -#need toget to consensus -signing_request_provision = (O) !tfgrid.workloads.reservation.signing.request.1 -signing_request_delete = (O) !tfgrid.workloads.reservation.signing.request.1 -containers = (LO) !tfgrid.workloads.reservation.container.1 -volumes = (LO) !tfgrid.workloads.reservation.volume.1 -zdbs = (LO) !tfgrid.workloads.reservation.zdb.1 -networks = (LO) !tfgrid.workloads.reservation.network.1 -kubernetes = (LO) !tfgrid.workloads.reservation.k8s.1 -#till whe is request for provisioning valid, if not signed in required time then obsolete -expiration_provisioning = (T) -#till whe is reservation valid -expiration_reservation = (T) - -@url = tfgrid.workloads.reservation.signing.request.1 -#part of the reservation.data, because should never be possible to delete this -#threebotids of people who can sign -signers = (LI) -#min nr of people who need to sign -quorum_min = (I) - -@url = tfgrid.workloads.reservation.signing.signature.1 -#threebotid -tid = (I) -#signature in string form of the json as stored in the root of this obj -signature = (S) -#time of signature -epoch = (T) diff --git a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_container_1.toml b/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_container_1.toml deleted file mode 100644 index 2950dd30c..000000000 --- a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_container_1.toml +++ /dev/null @@ -1,51 +0,0 @@ - - -@url = tfgrid.workloads.reservation.container.1 -#unique id inside the reservation is an autoincrement -workload_id = (I) -#links to unique node on the TFGrid -node_id = (S) -flist = (S) -hub_url = (S) -#env variables -environment = (dict) -#secret_env variables. the key is in plain text but the value -#must be encrypted using Curve25519 with the public key of node. -#then hex encoded. -secret_environment = (dict) -#the process to start -entrypoint = (S) -#corex yes or no -interactive = true (B) -volumes = (LO) !tfgrid.workloads.reservation.container.mount.1 -network_connection = (LO) !tfgrid.workloads.reservation.network.connection.1 -#where to send the statistics too -stats_aggregator = (LO) !tfgrid.workloads.reservation.statsaggregator.1 -#id of threebot who is the farmer -farmer_tid = (I) -logs = (LO) !tfgrid.workloads.reservation.container.logs.1 -capacity = (O) !tfgrid.workloads.reservation.container.capacity.1 - - -@url = tfgrid.workloads.reservation.container.mount.1 -volume_id = (S) -mountpoint = (S) - -@url = tfgrid.workloads.reservation.network.connection.1 -network_id = (S) -ipaddress = (ipaddress) -public_ip6 = (B) - -@url = tfgrid.workloads.reservation.container.logs.1 -type = (S) -data = (LO) !tfgrid.workloads.reservation.container.logsredis.1 - -@url = tfgrid.workloads.reservation.container.logsredis.1 -stdout = (S) -stderr = (S) - -@url = tfgrid.workloads.reservation.container.capacity.1 -# Number of vCPU -cpu = (I) -# memory in MiB -memory = (I) diff --git a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_k8s.toml b/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_k8s.toml deleted file mode 100644 index 7f2215ce8..000000000 --- a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_k8s.toml +++ /dev/null @@ -1,22 +0,0 @@ -@url = tfgrid.workloads.reservation.k8s.1 -#unique id inside the reservation is an autoincrement -workload_id = (I) -#links to unique node on the TFGrid -node_id = (S) -# defines the amount of vCpu, memory, and the disk size -# size 1: 1 vCpu, 2 GiB RAM, 50 GB disk -# size 2: 2 vCpu, 4 GiB RAM, 100 GB disk -size = (I) -network_id = (S) -ipaddress = (ipaddress) -# ClusterSecret is the hex encoded encrypted cluster secret -cluster_secret = (S) -# master_ips define the URL's for the kubernetes master nodes. If this -# list is empty, this node is considered to be a master node. -master_ips = (Lipaddress) -# list of public ssh keys to add to the VM -ssh_keys = (LS) -#where to send the statistics too -stats_aggregator = (LO) !tfgrid.workloads.reservation.statsaggregator.1 -#id of threebot who is the farmer -farmer_tid = (I) diff --git a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_network_1.toml b/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_network_1.toml deleted file mode 100644 index 3a4b854a7..000000000 --- a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_network_1.toml +++ /dev/null @@ -1,28 +0,0 @@ -@url = tfgrid.workloads.reservation.network.1 -name** = "" (S) -#unique id inside the reservation is an autoincrement (USE AS NET_ID) -workload_id = (I) -iprange = "10.10.0.0/16" (iprange) -#where to send the statistics too -stats_aggregator = (LO) !tfgrid.workloads.reservation.statsaggregator.1 -network_resources = (LO) !tfgrid.workloads.network.net_resource.1 -farmer_tid = (I) - - -@url = tfgrid.workloads.network.net_resource.1 -node_id = (S) -wireguard_private_key_encrypted = "" (S) # hex encoded encrypted with public key of the node, generated by the tfgrid customer -wireguard_public_key = "" (S) -wireguard_listen_port = (I) -iprange = "10.10.10.0/24" (iprange) # the wireguard has always nr 1 in the iprange, needs to correspond with container -peers = (LO) !tfgrid.workloads.wireguard.peer.1 - -@url = tfgrid.workloads.wireguard.peer.1 -public_key = "" (S) -allowed_iprange = "" (Liprange) # is the the same as iprange in the net_resource -endpoint = "" (S) # optional, only needed to connect out -iprange = "10.10.11.0/24" (iprange) - - - -#### ipv4 fake class B address definition per network diff --git a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_result_1.toml b/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_result_1.toml deleted file mode 100644 index e459aaf24..000000000 --- a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_result_1.toml +++ /dev/null @@ -1,11 +0,0 @@ -#json representation of whatever needs to come back from the ZOS after provisioning -@url = tfgrid.workloads.reservation.result.1 -category = "zdb,container,network,volume" (E) -workload_id = (S) -data_json = (S) -#signature of the data (is e.g. json) by the zos node who deployed -signature = (bytes) -state = "error,ok,deleted" (E) -message = "" (S) -epoch = (T) -node_id = (S) diff --git a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_statsaggregator_1.toml b/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_statsaggregator_1.toml deleted file mode 100644 index fb4e5912b..000000000 --- a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_statsaggregator_1.toml +++ /dev/null @@ -1,6 +0,0 @@ - -@url = tfgrid.workloads.reservation.statsaggregator.1 -#ipaddress of redis aggregator, can be hostname or ipaddr, string for now -addr = (S) -port = (I) -secret = (S) diff --git a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_volume_1.toml b/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_volume_1.toml deleted file mode 100644 index 079df2011..000000000 --- a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_volume_1.toml +++ /dev/null @@ -1,12 +0,0 @@ - - -@url = tfgrid.workloads.reservation.volume.1 -#unique id inside the reservation is an autoincrement -workload_id = (I) -node_id = (S) -size = (I) -type = "HDD,SSD" (E) -#where to send the statistics too -stats_aggregator = (LO) !tfgrid.workloads.reservation.statsaggregator.1 -#id of threebot who is the farmer -farmer_tid = (I) diff --git a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_workload_1.toml b/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_workload_1.toml deleted file mode 100644 index 5b340079b..000000000 --- a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_workload_1.toml +++ /dev/null @@ -1,9 +0,0 @@ -@url = tfgrid.workloads.reservation.workload.1 -workload_id = (S) -user = (S) -type = "zdb,container,volume,network,kubernetes" (E) -content = (dict) -created = (T) -duration = (I) -signature = (S) -to_delete = (B) diff --git a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_zdb_1.toml b/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_zdb_1.toml deleted file mode 100644 index 9a75d9028..000000000 --- a/tools/explorer/models/schema/workloads/tfgrid_workloads_reservation_zdb_1.toml +++ /dev/null @@ -1,15 +0,0 @@ - - -@url = tfgrid.workloads.reservation.zdb.1 -#unique id inside the reservation is an autoincrement -workload_id = (I) -node_id = (S) -size = (I) -mode = "seq,user" (E) -password = (S) -disk_type = "hdd,ssd" (E) -public = false (B) -#where to send the statistics too -stats_aggregator = (LO) !tfgrid.workloads.reservation.statsaggregator.1 -#id of threebot who is the farmer -farmer_tid = (I) diff --git a/tools/explorer/models/utils.go b/tools/explorer/models/utils.go deleted file mode 100644 index e30c7fec3..000000000 --- a/tools/explorer/models/utils.go +++ /dev/null @@ -1,77 +0,0 @@ -package models - -import ( - "math" - "net/http" - "strconv" - - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const ( - // DefaultPageSize is default page size - DefaultPageSize int64 = 100 -) - -// Pager is the find options wrapper -type Pager *options.FindOptions - -// Page creates a Pager -func Page(p int64, size ...int64) Pager { - ps := DefaultPageSize - if len(size) > 0 { - ps = size[0] - } - skip := p * ps - return options.Find().SetLimit(ps).SetSkip(skip).SetSort(bson.D{{Key: "_id", Value: 1}}) -} - -// PageFromRequest return page information from the page & size url params -func PageFromRequest(r *http.Request) Pager { - var ( - p = r.FormValue("page") - s = r.FormValue("size") - - page int64 - size = DefaultPageSize - ) - - if len(p) != 0 { - page, _ = strconv.ParseInt(p, 10, 64) - // user facing pages are start from 1 - page-- - if page < 0 { - page = 0 - } - } - if len(s) != 0 { - size, _ = strconv.ParseInt(s, 10, 64) - } - - // make sure user doesn't kill the server by returning too much data - if size > 1000 { - size = 1000 - } - - return Page(page, size) -} - -// Pages return number of pages based on the total number -func Pages(p Pager, total int64) int64 { - return int64(math.Ceil(float64(total) / float64(*p.Limit))) -} - -// NrPages compute the number of page of a collection -func NrPages(total, pageSize int64) int64 { - return int64(math.Ceil(float64(total) / float64(pageSize))) -} - -// QueryInt get integer from query string -func QueryInt(r *http.Request, q string) (int64, error) { - s := r.URL.Query().Get(q) - if s != "" { - return strconv.ParseInt(s, 10, 64) - } - return 0, nil -} diff --git a/tools/explorer/mw/action.go b/tools/explorer/mw/action.go deleted file mode 100644 index 61ae7a70c..000000000 --- a/tools/explorer/mw/action.go +++ /dev/null @@ -1,143 +0,0 @@ -package mw - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/rs/zerolog/log" -) - -// Response interface -type Response interface { - Status() int - Err() error - - // header getter - Header() http.Header - // header setter - WithHeader(k, v string) Response -} - -// Action interface -type Action func(r *http.Request) (interface{}, Response) - -// AsHandlerFunc is a helper wrapper to make implementing actions easier -func AsHandlerFunc(a Action) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - object, result := a(r) - - w.Header().Set("Content-Type", "application/json") - w.Header().Set("access-control-expose-headers", "pages") - - if result == nil { - w.WriteHeader(http.StatusOK) - } else { - - h := result.Header() - for k := range h { - for _, v := range h.Values(k) { - w.Header().Add(k, v) - } - } - - w.WriteHeader(result.Status()) - if err := result.Err(); err != nil { - log.Error().Msgf("%s", err.Error()) - object = struct { - Error string `json:"error"` - }{ - Error: err.Error(), - } - } - } - - if err := json.NewEncoder(w).Encode(object); err != nil { - log.Error().Err(err).Msg("failed to encode return object") - } - } -} - -type genericResponse struct { - status int - err error - header http.Header -} - -func (r genericResponse) Status() int { - return r.status -} - -func (r genericResponse) Err() error { - return r.err -} - -func (r genericResponse) Header() http.Header { - if r.header == nil { - r.header = http.Header{} - } - return r.header -} - -func (r genericResponse) WithHeader(k, v string) Response { - if r.header == nil { - r.header = http.Header{} - } - - r.header.Add(k, v) - return r -} - -// Created return a created response -func Created() Response { - return genericResponse{status: http.StatusCreated} -} - -// Ok return a ok response -func Ok() Response { - return genericResponse{status: http.StatusOK} -} - -// Error generic error response -func Error(err error, code ...int) Response { - status := http.StatusInternalServerError - if len(code) > 0 { - status = code[0] - } - - if err == nil { - err = fmt.Errorf("no message") - } - - return genericResponse{status: status, err: err} -} - -// BadRequest result -func BadRequest(err error) Response { - return Error(err, http.StatusBadRequest) -} - -// NotFound response -func NotFound(err error) Response { - return Error(err, http.StatusNotFound) -} - -// Conflict response -func Conflict(err error) Response { - return Error(err, http.StatusConflict) -} - -// UnAuthorized response -func UnAuthorized(err error) Response { - return Error(err, http.StatusUnauthorized) -} - -// Forbidden response -func Forbidden(err error) Response { - return Error(err, http.StatusForbidden) -} - -// NoContent response -func NoContent() Response { - return genericResponse{status: http.StatusNoContent} -} diff --git a/tools/explorer/mw/auth.go b/tools/explorer/mw/auth.go deleted file mode 100644 index c5a3f56c9..000000000 --- a/tools/explorer/mw/auth.go +++ /dev/null @@ -1,124 +0,0 @@ -package mw - -import ( - "context" - "crypto/ed25519" - "encoding/hex" - "encoding/json" - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/jbenet/go-base58" - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/pkg/phonebook/types" - "github.com/zaibon/httpsig" - - "go.mongodb.org/mongo-driver/mongo" -) - -// UserKeyGetter implements httpsig.KeyGetter for the users collections -type UserKeyGetter struct { - db *mongo.Database -} - -// NewUserKeyGetter create a httpsig.KeyGetter that uses the users collection -// to find the key -func NewUserKeyGetter(db *mongo.Database) UserKeyGetter { - return UserKeyGetter{db: db} -} - -// GetKey implements httpsig.KeyGetter -func (u UserKeyGetter) GetKey(id string) interface{} { - ctx := context.TODO() - - uid, err := strconv.ParseInt(id, 10, 64) - if err != nil { - return nil - } - - f := types.UserFilter{} - f = f.WithID(schema.ID(uid)) - - user, err := f.Get(ctx, u.db) - if err != nil { - return nil - } - - pk, err := hex.DecodeString(user.Pubkey) - if err != nil { - return nil - } - return ed25519.PublicKey(pk) -} - -// NodeKeyGetter implements httpsig.KeyGetter for the nodes collections -type NodeKeyGetter struct{} - -// NewNodeKeyGetter create a httpsig.KeyGetter that uses the nodes collection -// to find the key -func NewNodeKeyGetter() NodeKeyGetter { - return NodeKeyGetter{} -} - -// GetKey implements httpsig.KeyGetter -func (m NodeKeyGetter) GetKey(id string) interface{} { - // the node ID is its public key base58 encoded, so we just need - // to decode it to get the []byte version of the key - return ed25519.PublicKey(base58.Decode(id)) -} - -// requiredHeaders are the parameters to be used to generated the http signature -var requiredHeaders = []string{"(created)", "date", "threebot-id"} - -// AuthMiddleware implements https://tools.ietf.org/html/draft-cavage-http-signatures-12 -// authentication scheme as an HTTP middleware -type AuthMiddleware struct { - verifier *httpsig.Verifier -} - -// NewAuthMiddleware creates a new AuthMiddleware using the v httpsig.Verifier -func NewAuthMiddleware(v *httpsig.Verifier) *AuthMiddleware { - v.SetRequiredHeaders(requiredHeaders) - return &AuthMiddleware{ - verifier: v, - } -} - -// Middleware implements mux.Middlware interface -func (a *AuthMiddleware) Middleware(handler http.Handler) http.Handler { - var challengeParams []string - if headers := a.verifier.RequiredHeaders(); len(headers) > 0 { - challengeParams = append(challengeParams, - fmt.Sprintf("headers=%q", strings.Join(headers, " "))) - } - - challenge := "Signature" - if len(challengeParams) > 0 { - challenge += fmt.Sprintf(" %s", strings.Join(challengeParams, ", ")) - } - - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - keyID, err := a.verifier.Verify(req) - if err != nil { - w.Header()["WWW-Authenticate"] = []string{challenge} - w.WriteHeader(http.StatusUnauthorized) - - log.Error().Err(err).Msgf("unauthorized access to %s", req.URL.Path) - - object := struct { - Error string `json:"error"` - }{ - Error: errors.Wrap(err, "unauthorized access").Error(), - } - if err := json.NewEncoder(w).Encode(object); err != nil { - log.Error().Err(err).Msg("failed to encode return object") - } - return - } - handler.ServeHTTP(w, req.WithContext(httpsig.WithKeyID(req.Context(), keyID))) - }) -} diff --git a/tools/explorer/mw/db.go b/tools/explorer/mw/db.go deleted file mode 100644 index 6652c7114..000000000 --- a/tools/explorer/mw/db.go +++ /dev/null @@ -1,48 +0,0 @@ -package mw - -import ( - "context" - "net/http" - - "go.mongodb.org/mongo-driver/mongo" -) - -type ( - dbMiddlewareKey struct{} -) - -// DatabaseMiddleware middleware -type DatabaseMiddleware struct { - name string - client *mongo.Client -} - -// NewDatabaseMiddleware creates a new database middleware -func NewDatabaseMiddleware(dbName string, client *mongo.Client) (*DatabaseMiddleware, error) { - - return &DatabaseMiddleware{name: dbName, client: client}, nil -} - -// Middleware is the middleware function -func (d *DatabaseMiddleware) Middleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := context.WithValue(r.Context(), dbMiddlewareKey{}, d.client.Database(d.name)) - - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -// Database return database as configured in the middleware -func (d *DatabaseMiddleware) Database() *mongo.Database { - return d.client.Database(d.name) -} - -// Database gets the database configured on the request -func Database(r *http.Request) *mongo.Database { - v := r.Context().Value(dbMiddlewareKey{}) - if v == nil { - panic("DatabaseMiddleware is not configured") - } - - return v.(*mongo.Database) -} diff --git a/tools/explorer/pkg/directory/farms_handlers.go b/tools/explorer/pkg/directory/farms_handlers.go deleted file mode 100644 index b1ced3820..000000000 --- a/tools/explorer/pkg/directory/farms_handlers.go +++ /dev/null @@ -1,122 +0,0 @@ -package directory - -import ( - "encoding/json" - "fmt" - "net/http" - "strconv" - - "github.com/rs/zerolog/log" - "github.com/zaibon/httpsig" - - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models" - "github.com/threefoldtech/zos/tools/explorer/mw" - directory "github.com/threefoldtech/zos/tools/explorer/pkg/directory/types" - - "github.com/gorilla/mux" -) - -func (s *FarmAPI) registerFarm(r *http.Request) (interface{}, mw.Response) { - log.Info().Msg("farm register request received") - - db := mw.Database(r) - defer r.Body.Close() - - var info directory.Farm - if err := json.NewDecoder(r.Body).Decode(&info); err != nil { - return nil, mw.BadRequest(err) - } - - if err := info.Validate(); err != nil { - return nil, mw.BadRequest(err) - } - - id, err := s.Add(r.Context(), db, info) - if err != nil { - return nil, mw.Error(err) - } - - return struct { - ID schema.ID `json:"id"` - }{ - id, - }, mw.Created() -} - -func (s *FarmAPI) updateFarm(r *http.Request) (interface{}, mw.Response) { - sid := mux.Vars(r)["farm_id"] - - id, err := strconv.ParseInt(sid, 10, 64) - if err != nil { - return nil, mw.BadRequest(err) - } - - db := mw.Database(r) - - farm, err := s.GetByID(r.Context(), db, id) - if err != nil { - return nil, mw.NotFound(err) - } - - sfarmerID := httpsig.KeyIDFromContext(r.Context()) - requestFarmerID, err := strconv.ParseInt(sfarmerID, 10, 64) - if err != nil { - return nil, mw.BadRequest(err) - } - - if farm.ThreebotId != requestFarmerID { - return nil, mw.Forbidden(fmt.Errorf("only the farm owner can update the information of its farm")) - } - - var info directory.Farm - if err := json.NewDecoder(r.Body).Decode(&info); err != nil { - return nil, mw.BadRequest(err) - } - - info.ID = schema.ID(id) - - err = s.Update(r.Context(), db, info.ID, info) - if err != nil { - return nil, mw.Error(err) - } - - return nil, mw.Ok() -} - -func (s *FarmAPI) listFarm(r *http.Request) (interface{}, mw.Response) { - q := directory.FarmQuery{} - if err := q.Parse(r); err != nil { - return nil, err - } - var filter directory.FarmFilter - filter = filter.WithFarmQuery(q) - db := mw.Database(r) - - pager := models.PageFromRequest(r) - farms, total, err := s.List(r.Context(), db, filter, pager) - if err != nil { - return nil, mw.Error(err) - } - - pages := fmt.Sprintf("%d", models.Pages(pager, total)) - return farms, mw.Ok().WithHeader("Pages", pages) -} - -func (s *FarmAPI) getFarm(r *http.Request) (interface{}, mw.Response) { - sid := mux.Vars(r)["farm_id"] - - id, err := strconv.ParseInt(sid, 10, 64) - if err != nil { - return nil, mw.BadRequest(err) - } - - db := mw.Database(r) - - farm, err := s.GetByID(r.Context(), db, id) - if err != nil { - return nil, mw.NotFound(err) - } - - return farm, nil -} diff --git a/tools/explorer/pkg/directory/farms_store.go b/tools/explorer/pkg/directory/farms_store.go deleted file mode 100644 index 19df4faff..000000000 --- a/tools/explorer/pkg/directory/farms_store.go +++ /dev/null @@ -1,69 +0,0 @@ -package directory - -import ( - "context" - - "github.com/pkg/errors" - "github.com/threefoldtech/zos/pkg/schema" - directory "github.com/threefoldtech/zos/tools/explorer/pkg/directory/types" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -// FarmAPI holds farm releated handlers -type FarmAPI struct{} - -// List farms -// TODO: add paging arguments -func (s *FarmAPI) List(ctx context.Context, db *mongo.Database, filter directory.FarmFilter, opts ...*options.FindOptions) ([]directory.Farm, int64, error) { - - cur, err := filter.Find(ctx, db, opts...) - if err != nil { - return nil, 0, errors.Wrap(err, "failed to list farms") - } - defer cur.Close(ctx) - out := []directory.Farm{} - if err := cur.All(ctx, &out); err != nil { - return nil, 0, errors.Wrap(err, "failed to load farm list") - } - - count, err := filter.Count(ctx, db) - if err != nil { - return nil, 0, errors.Wrap(err, "failed to count entries in farms collection") - } - - return out, count, nil -} - -// GetByName gets a farm by name -func (s *FarmAPI) GetByName(ctx context.Context, db *mongo.Database, name string) (directory.Farm, error) { - var filter directory.FarmFilter - filter = filter.WithName(name) - - return filter.Get(ctx, db) -} - -// GetByID gets a farm by ID -func (s *FarmAPI) GetByID(ctx context.Context, db *mongo.Database, id int64) (directory.Farm, error) { - var filter directory.FarmFilter - filter = filter.WithID(schema.ID(id)) - - return filter.Get(ctx, db) -} - -// Add add farm to store -func (s *FarmAPI) Add(ctx context.Context, db *mongo.Database, farm directory.Farm) (schema.ID, error) { - return directory.FarmCreate(ctx, db, farm) -} - -// Update farm information -func (s *FarmAPI) Update(ctx context.Context, db *mongo.Database, id schema.ID, farm directory.Farm) error { - return directory.FarmUpdate(ctx, db, id, farm) -} - -// Delete deletes a farm by ID -func (s FarmAPI) Delete(ctx context.Context, db *mongo.Database, id int64) error { - var filter directory.FarmFilter - filter = filter.WithID(schema.ID(id)) - return filter.Delete(ctx, db) -} diff --git a/tools/explorer/pkg/directory/node_handlers.go b/tools/explorer/pkg/directory/node_handlers.go deleted file mode 100644 index 1afbe53d2..000000000 --- a/tools/explorer/pkg/directory/node_handlers.go +++ /dev/null @@ -1,318 +0,0 @@ -package directory - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "strconv" - - "github.com/rs/zerolog/log" - "github.com/zaibon/httpsig" - "go.mongodb.org/mongo-driver/mongo" - - "github.com/threefoldtech/zos/pkg/capacity" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models" - generated "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" - "github.com/threefoldtech/zos/tools/explorer/mw" - "github.com/threefoldtech/zos/tools/explorer/pkg/directory/types" - directory "github.com/threefoldtech/zos/tools/explorer/pkg/directory/types" - - "github.com/gorilla/mux" - "github.com/threefoldtech/zos/pkg/capacity/dmi" -) - -func (s *NodeAPI) registerNode(r *http.Request) (interface{}, mw.Response) { - log.Info().Msg("node register request received") - - defer r.Body.Close() - - var n directory.Node - if err := json.NewDecoder(r.Body).Decode(&n); err != nil { - return nil, mw.BadRequest(err) - } - - //make sure node can not set public config - n.PublicConfig = nil - db := mw.Database(r) - if _, err := s.Add(r.Context(), db, n); err != nil { - if errors.Is(err, mongo.ErrNoDocuments) { - return nil, mw.NotFound(fmt.Errorf("farm with id:%d does not exists", n.FarmId)) - } - return nil, mw.Error(err) - } - - log.Info().Msgf("node registered: %+v\n", n) - - return nil, mw.Created() -} - -func (s *NodeAPI) nodeDetail(r *http.Request) (interface{}, mw.Response) { - nodeID := mux.Vars(r)["node_id"] - q := nodeQuery{} - if err := q.Parse(r); err != nil { - return nil, err - } - db := mw.Database(r) - - node, err := s.Get(r.Context(), db, nodeID, q.Proofs) - if err != nil { - return nil, mw.NotFound(err) - } - - return node, nil -} - -func (s *NodeAPI) listNodes(r *http.Request) (interface{}, mw.Response) { - q := nodeQuery{} - if err := q.Parse(r); err != nil { - return nil, err - } - - db := mw.Database(r) - pager := models.PageFromRequest(r) - nodes, total, err := s.List(r.Context(), db, q, pager) - if err != nil { - return nil, mw.Error(err) - } - - pages := fmt.Sprintf("%d", models.Pages(pager, total)) - return nodes, mw.Ok().WithHeader("Pages", pages) -} - -func (s *NodeAPI) registerCapacity(r *http.Request) (interface{}, mw.Response) { - x := struct { - Capacity generated.ResourceAmount `json:"capacity,omitempty"` - DMI dmi.DMI `json:"dmi,omitempty"` - Disks capacity.Disks `json:"disks,omitempty"` - Hypervisor []string `json:"hypervisor,omitempty"` - }{} - - defer r.Body.Close() - - if err := json.NewDecoder(r.Body).Decode(&x); err != nil { - return nil, mw.BadRequest(err) - } - - nodeID := mux.Vars(r)["node_id"] - hNodeID := httpsig.KeyIDFromContext(r.Context()) - if nodeID != hNodeID { - return nil, mw.Forbidden(fmt.Errorf("trying to register capacity for nodeID %s while you are %s", nodeID, hNodeID)) - } - - db := mw.Database(r) - - if err := s.updateTotalCapacity(r.Context(), db, nodeID, x.Capacity); err != nil { - return nil, mw.NotFound(err) - } - - if err := s.StoreProof(r.Context(), db, nodeID, x.DMI, x.Disks, x.Hypervisor); err != nil { - return nil, mw.Error(err) - } - - return nil, nil -} - -func (s *NodeAPI) registerIfaces(r *http.Request) (interface{}, mw.Response) { - log.Debug().Msg("network interfaces register request received") - - defer r.Body.Close() - - var input []generated.Iface - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - return nil, mw.BadRequest(err) - } - - nodeID := mux.Vars(r)["node_id"] - hNodeID := httpsig.KeyIDFromContext(r.Context()) - if nodeID != hNodeID { - return nil, mw.Forbidden(fmt.Errorf("trying to register interfaces for nodeID %s while you are %s", nodeID, hNodeID)) - } - - db := mw.Database(r) - if err := s.SetInterfaces(r.Context(), db, nodeID, input); err != nil { - return nil, mw.Error(err) - } - - return nil, mw.Created() -} - -func (s *NodeAPI) configurePublic(r *http.Request) (interface{}, mw.Response) { - var iface generated.PublicIface - - defer r.Body.Close() - if err := json.NewDecoder(r.Body).Decode(&iface); err != nil { - return nil, mw.BadRequest(err) - } - - db := mw.Database(r) - nodeID := mux.Vars(r)["node_id"] - - node, err := s.Get(r.Context(), db, nodeID, false) - if err != nil { - return nil, mw.NotFound(err) - } - - // ensure it is the farmer that does the call - authorized, merr := isFarmerAuthorized(r, node, db) - if err != nil { - return nil, merr - } - - if !authorized { - return nil, mw.Forbidden(fmt.Errorf("only the farmer can configured the public interface of its nodes")) - } - - if err := s.SetPublicConfig(r.Context(), db, nodeID, iface); err != nil { - return nil, mw.Error(err) - } - - return nil, mw.Created() -} - -func (s *NodeAPI) configureFreeToUse(r *http.Request) (interface{}, mw.Response) { - db := mw.Database(r) - nodeID := mux.Vars(r)["node_id"] - - node, err := s.Get(r.Context(), db, nodeID, false) - if err != nil { - return nil, mw.NotFound(err) - } - - // ensure it is the farmer that does the call - authorized, merr := isFarmerAuthorized(r, node, db) - if err != nil { - return nil, merr - } - - if !authorized { - return nil, mw.Forbidden(fmt.Errorf("only the farmer can configured the if the node is free to use")) - } - - choice := struct { - FreeToUse bool `json:"free_to_use"` - }{} - - defer r.Body.Close() - if err := json.NewDecoder(r.Body).Decode(&choice); err != nil { - return nil, mw.BadRequest(err) - } - - if err := s.updateFreeToUse(r.Context(), db, node.NodeId, choice.FreeToUse); err != nil { - return nil, mw.Error(err) - } - - return nil, mw.Ok() -} - -func (s *NodeAPI) registerPorts(r *http.Request) (interface{}, mw.Response) { - - defer r.Body.Close() - - nodeID := mux.Vars(r)["node_id"] - hNodeID := httpsig.KeyIDFromContext(r.Context()) - if nodeID != hNodeID { - return nil, mw.Forbidden(fmt.Errorf("trying to register ports for nodeID %s while you are %s", nodeID, hNodeID)) - } - - input := struct { - Ports []uint `json:"ports"` - }{} - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - return nil, mw.BadRequest(err) - } - - log.Debug().Uints("ports", input.Ports).Msg("wireguard ports received") - - db := mw.Database(r) - if err := s.SetWGPorts(r.Context(), db, nodeID, input.Ports); err != nil { - return nil, mw.NotFound(err) - } - - return nil, nil -} - -func (s *NodeAPI) updateUptimeHandler(r *http.Request) (interface{}, mw.Response) { - defer r.Body.Close() - - nodeID := mux.Vars(r)["node_id"] - hNodeID := httpsig.KeyIDFromContext(r.Context()) - if nodeID != hNodeID { - return nil, mw.Forbidden(fmt.Errorf("trying to register uptime for nodeID %s while you are %s", nodeID, hNodeID)) - } - - input := struct { - Uptime uint64 `json:"uptime"` - }{} - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - return nil, mw.BadRequest(err) - } - - db := mw.Database(r) - log.Debug().Str("node", nodeID).Uint64("uptime", input.Uptime).Msg("node uptime received") - - if err := s.updateUptime(r.Context(), db, nodeID, int64(input.Uptime)); err != nil { - return nil, mw.NotFound(err) - } - - return nil, nil -} - -func (s *NodeAPI) updateReservedResources(r *http.Request) (interface{}, mw.Response) { - defer r.Body.Close() - - nodeID := mux.Vars(r)["node_id"] - hNodeID := httpsig.KeyIDFromContext(r.Context()) - if nodeID != hNodeID { - return nil, mw.Forbidden(fmt.Errorf("trying to update reserved capacity for nodeID %s while you are %s", nodeID, hNodeID)) - } - - input := struct { - generated.ResourceAmount - generated.WorkloadAmount - }{} - - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - return nil, mw.BadRequest(err) - } - - db := mw.Database(r) - if err := s.updateReservedCapacity(r.Context(), db, nodeID, input.ResourceAmount); err != nil { - return nil, mw.NotFound(err) - } - if err := s.updateWorkloadsAmount(r.Context(), db, nodeID, input.WorkloadAmount); err != nil { - return nil, mw.NotFound(err) - } - - return nil, nil -} - -func farmOwner(ctx context.Context, farmID int64, db *mongo.Database) (int64, mw.Response) { - ff := types.FarmFilter{} - ff = ff.WithID(schema.ID(farmID)) - - farm, err := ff.Get(ctx, db) - if err != nil { - return 0, mw.Error(err) //TODO - } - - return farm.ThreebotId, nil -} - -// isFarmerAuthorized ensure it is the farmer authenticated in request r is owning the node -func isFarmerAuthorized(r *http.Request, node directory.Node, db *mongo.Database) (bool, mw.Response) { - actualFarmerID, merr := farmOwner(r.Context(), node.FarmId, db) - if merr != nil { - return false, merr - } - - sfarmerID := httpsig.KeyIDFromContext(r.Context()) - requestFarmerID, err := strconv.ParseInt(sfarmerID, 10, 64) - if err != nil { - return false, mw.BadRequest(err) - } - log.Debug().Int64("actualFarmerID", actualFarmerID).Int64("requestFarmID", requestFarmerID).Send() - return (requestFarmerID == actualFarmerID), nil -} diff --git a/tools/explorer/pkg/directory/nodes_store.go b/tools/explorer/pkg/directory/nodes_store.go deleted file mode 100644 index c8bce8533..000000000 --- a/tools/explorer/pkg/directory/nodes_store.go +++ /dev/null @@ -1,260 +0,0 @@ -package directory - -import ( - "context" - "crypto/md5" - "encoding/json" - "fmt" - "net/http" - "sort" - "time" - - "github.com/gorilla/mux" - "github.com/pkg/errors" - "github.com/threefoldtech/zos/pkg/capacity" - "github.com/threefoldtech/zos/pkg/capacity/dmi" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models" - generated "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" - "github.com/threefoldtech/zos/tools/explorer/mw" - directory "github.com/threefoldtech/zos/tools/explorer/pkg/directory/types" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -// NodeAPI holds api for nodes -type NodeAPI struct{} - -type nodeQuery struct { - FarmID int64 - Country string - City string - CRU int64 - MRU int64 - SRU int64 - HRU int64 - Proofs bool -} - -func (n *nodeQuery) Parse(r *http.Request) mw.Response { - var err error - n.FarmID, err = models.QueryInt(r, "farm") - if err != nil { - return mw.BadRequest(errors.Wrap(err, "invalid farm id")) - } - n.Country = r.URL.Query().Get("country") - n.City = r.URL.Query().Get("city") - n.CRU, err = models.QueryInt(r, "cru") - if err != nil { - return mw.BadRequest(errors.Wrap(err, "invalid cru")) - } - n.MRU, err = models.QueryInt(r, "mru") - if err != nil { - return mw.BadRequest(errors.Wrap(err, "invalid mru")) - } - n.SRU, err = models.QueryInt(r, "sru") - if err != nil { - return mw.BadRequest(errors.Wrap(err, "invalid sru")) - } - n.HRU, err = models.QueryInt(r, "hru") - if err != nil { - return mw.BadRequest(errors.Wrap(err, "invalid hru")) - } - n.Proofs = r.URL.Query().Get("proofs") == "true" - - return nil -} - -// List farms -// TODO: add paging arguments -func (s *NodeAPI) List(ctx context.Context, db *mongo.Database, q nodeQuery, opts ...*options.FindOptions) ([]directory.Node, int64, error) { - var filter directory.NodeFilter - if q.FarmID > 0 { - filter = filter.WithFarmID(schema.ID(q.FarmID)) - } - filter = filter.WithTotalCap(q.CRU, q.MRU, q.HRU, q.SRU) - filter = filter.WithLocation(q.Country, q.City) - - if !q.Proofs { - projection := bson.D{ - {Key: "proofs", Value: 0}, - } - opts = append(opts, options.Find().SetProjection(projection)) - } - - cur, err := filter.Find(ctx, db, opts...) - if err != nil { - return nil, 0, errors.Wrap(err, "failed to list nodes") - } - - defer cur.Close(ctx) - out := []directory.Node{} - if err := cur.All(ctx, &out); err != nil { - return nil, 0, errors.Wrap(err, "failed to load node list") - } - - count, err := filter.Count(ctx, db) - if err != nil { - return nil, 0, errors.Wrap(err, "failed to count entries in nodes collection") - } - - return out, count, nil -} - -// Get a single node -func (s *NodeAPI) Get(ctx context.Context, db *mongo.Database, nodeID string, includeProofs bool) (directory.Node, error) { - var filter directory.NodeFilter - filter = filter.WithNodeID(nodeID) - return filter.Get(ctx, db, includeProofs) -} - -// Exists tests if node exists -func (s *NodeAPI) Exists(ctx context.Context, db *mongo.Database, nodeID string) (bool, error) { - var filter directory.NodeFilter - filter = filter.WithNodeID(nodeID) - - count, err := filter.Count(ctx, db) - if err != nil { - return false, err - } - - return count > 0, nil -} - -// Count counts the number of document in the collection -func (s *NodeAPI) Count(ctx context.Context, db *mongo.Database, filter directory.NodeFilter) (int64, error) { - return filter.Count(ctx, db) -} - -// Add a node to the store -func (s *NodeAPI) Add(ctx context.Context, db *mongo.Database, node directory.Node) (schema.ID, error) { - return directory.NodeCreate(ctx, db, node) -} - -func (s *NodeAPI) updateTotalCapacity(ctx context.Context, db *mongo.Database, nodeID string, capacity generated.ResourceAmount) error { - return directory.NodeUpdateTotalResources(ctx, db, nodeID, capacity) -} - -func (s *NodeAPI) updateReservedCapacity(ctx context.Context, db *mongo.Database, nodeID string, capacity generated.ResourceAmount) error { - return directory.NodeUpdateReservedResources(ctx, db, nodeID, capacity) -} - -func (s *NodeAPI) updateUptime(ctx context.Context, db *mongo.Database, nodeID string, uptime int64) error { - return directory.NodeUpdateUptime(ctx, db, nodeID, uptime) -} - -func (s *NodeAPI) updateFreeToUse(ctx context.Context, db *mongo.Database, nodeID string, freeToUse bool) error { - return directory.NodeUpdateFreeToUse(ctx, db, nodeID, freeToUse) -} - -func (s *NodeAPI) updateWorkloadsAmount(ctx context.Context, db *mongo.Database, nodeID string, workloads generated.WorkloadAmount) error { - return directory.NodeUpdateWorkloadsAmount(ctx, db, nodeID, workloads) -} - -// StoreProof stores node hardware proof -func (s *NodeAPI) StoreProof(ctx context.Context, db *mongo.Database, nodeID string, dmi dmi.DMI, disks capacity.Disks, hypervisor []string) error { - var err error - proof := generated.Proof{ - Created: schema.Date{Time: time.Now()}, - Hypervisor: hypervisor, - } - - proof.Hardware = map[string]interface{}{ - "sections": dmi.Sections, - "tooling": dmi.Tooling, - } - proof.HardwareHash, err = hashProof(proof.Hardware) - if err != nil { - return err - } - - proof.Disks = map[string]interface{}{ - "aggregator": disks.Aggregator, - "environment": disks.Environment, - "devices": disks.Devices, - "tool": disks.Tool, - } - proof.DiskHash, err = hashProof(proof.Disks) - if err != nil { - return err - } - - return directory.NodePushProof(ctx, db, nodeID, proof) -} - -// SetInterfaces updates node interfaces -func (s *NodeAPI) SetInterfaces(ctx context.Context, db *mongo.Database, nodeID string, ifaces []generated.Iface) error { - return directory.NodeSetInterfaces(ctx, db, nodeID, ifaces) -} - -// SetPublicConfig sets node public config -func (s *NodeAPI) SetPublicConfig(ctx context.Context, db *mongo.Database, nodeID string, cfg generated.PublicIface) error { - node, err := s.Get(ctx, db, nodeID, false) - if err != nil { - return err - } - - if node.PublicConfig == nil { - cfg.Version = 1 - } else { - cfg.Version = node.PublicConfig.Version + 1 - } - - return directory.NodeSetPublicConfig(ctx, db, nodeID, cfg) -} - -// SetWGPorts sets node gateway ports -func (s *NodeAPI) SetWGPorts(ctx context.Context, db *mongo.Database, nodeID string, ports []uint) error { - return directory.NodeSetWGPorts(ctx, db, nodeID, ports) -} - -// Requires is a wrapper that makes sure node with that case exists before -// running the handler -func (s *NodeAPI) Requires(key string, handler mw.Action) mw.Action { - return func(r *http.Request) (interface{}, mw.Response) { - nodeID, ok := mux.Vars(r)[key] - if !ok { - // programming error, we should panic in this case - panic("invalid node-id key") - } - - db := mw.Database(r) - - exists, err := s.Exists(r.Context(), db, nodeID) - if err != nil { - return nil, mw.Error(err) - } else if !exists { - return nil, mw.NotFound(fmt.Errorf("node '%s' not found", nodeID)) - } - - return handler(r) - } -} - -// hashProof return the hex encoded md5 hash of the json encoded version of p -func hashProof(p map[string]interface{}) (string, error) { - - // we are trying to have always produce same hash for same content of p - // so we convert the map into a list so we can sort - // the key and workaround the fact that maps are not sorted - - type kv struct { - k string - v interface{} - } - - kvs := make([]kv, len(p)) - for k, v := range p { - kvs = append(kvs, kv{k: k, v: v}) - } - sort.Slice(kvs, func(i, j int) bool { return kvs[i].k < kvs[j].k }) - - b, err := json.Marshal(kvs) - if err != nil { - return "", err - } - h := md5.New() - bh := h.Sum(b) - return fmt.Sprintf("%x", bh), nil -} diff --git a/tools/explorer/pkg/directory/setup.go b/tools/explorer/pkg/directory/setup.go deleted file mode 100644 index b0e7c4b0e..000000000 --- a/tools/explorer/pkg/directory/setup.go +++ /dev/null @@ -1,52 +0,0 @@ -package directory - -import ( - "context" - - "github.com/gorilla/mux" - "github.com/threefoldtech/zos/tools/explorer/mw" - directory "github.com/threefoldtech/zos/tools/explorer/pkg/directory/types" - "github.com/zaibon/httpsig" - "go.mongodb.org/mongo-driver/mongo" -) - -// Setup injects and initializes directory package -func Setup(parent *mux.Router, db *mongo.Database) error { - if err := directory.Setup(context.TODO(), db); err != nil { - return err - } - - var farmAPI FarmAPI - farms := parent.PathPrefix("/farms").Subrouter() - farmsAuthenticated := parent.PathPrefix("/farms").Subrouter() - farmsAuthenticated.Use(mw.NewAuthMiddleware(httpsig.NewVerifier(mw.NewUserKeyGetter(db))).Middleware) - - farms.HandleFunc("", mw.AsHandlerFunc(farmAPI.registerFarm)).Methods("POST").Name("farm-register") - farms.HandleFunc("", mw.AsHandlerFunc(farmAPI.listFarm)).Methods("GET").Name("farm-list") - farms.HandleFunc("/{farm_id}", mw.AsHandlerFunc(farmAPI.getFarm)).Methods("GET").Name("farm-get") - farmsAuthenticated.HandleFunc("/{farm_id}", mw.AsHandlerFunc(farmAPI.updateFarm)).Methods("PUT").Name("farm-update") - - var nodeAPI NodeAPI - nodes := parent.PathPrefix("/nodes").Subrouter() - nodesAuthenticated := parent.PathPrefix("/nodes").Subrouter() - userAuthenticated := parent.PathPrefix("/nodes").Subrouter() - - nodeAuthMW := mw.NewAuthMiddleware(httpsig.NewVerifier(mw.NewNodeKeyGetter())) - userAuthMW := mw.NewAuthMiddleware(httpsig.NewVerifier(mw.NewUserKeyGetter(db))) - - userAuthenticated.Use(userAuthMW.Middleware) - nodesAuthenticated.Use(nodeAuthMW.Middleware) - - nodes.HandleFunc("", mw.AsHandlerFunc(nodeAPI.registerNode)).Methods("POST").Name("node-register") - nodes.HandleFunc("", mw.AsHandlerFunc(nodeAPI.listNodes)).Methods("GET").Name("nodes-list") - nodes.HandleFunc("/{node_id}", mw.AsHandlerFunc(nodeAPI.nodeDetail)).Methods("GET").Name(("node-get")) - nodesAuthenticated.HandleFunc("/{node_id}/interfaces", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.registerIfaces))).Methods("POST").Name("node-interfaces") - nodesAuthenticated.HandleFunc("/{node_id}/ports", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.registerPorts))).Methods("POST").Name("node-set-ports") - userAuthenticated.HandleFunc("/{node_id}/configure_public", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.configurePublic))).Methods("POST").Name("node-configure-public") - userAuthenticated.HandleFunc("/{node_id}/configure_free", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.configureFreeToUse))).Methods("POST").Name("node-configure-free") - nodesAuthenticated.HandleFunc("/{node_id}/capacity", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.registerCapacity))).Methods("POST").Name("node-capacity") - nodesAuthenticated.HandleFunc("/{node_id}/uptime", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.updateUptimeHandler))).Methods("POST").Name("node-uptime") - nodesAuthenticated.HandleFunc("/{node_id}/used_resources", mw.AsHandlerFunc(nodeAPI.Requires("node_id", nodeAPI.updateReservedResources))).Methods("POST").Name("node-reserved-resources") - - return nil -} diff --git a/tools/explorer/pkg/directory/types/farm.go b/tools/explorer/pkg/directory/types/farm.go deleted file mode 100644 index afa67ffcd..000000000 --- a/tools/explorer/pkg/directory/types/farm.go +++ /dev/null @@ -1,194 +0,0 @@ -package types - -import ( - "context" - "fmt" - "net/http" - "regexp" - - "github.com/pkg/errors" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/config" - "github.com/threefoldtech/zos/tools/explorer/models" - generated "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" - "github.com/threefoldtech/zos/tools/explorer/mw" - "github.com/threefoldtech/zos/tools/explorer/pkg/stellar" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -var ( - farmNamePattern = regexp.MustCompile("^[a-zA-Z0-9-_]+$") -) - -const ( - // FarmCollection db collection name - FarmCollection = "farm" -) - -//Farm mongo db wrapper for generated TfgridDirectoryFarm -type Farm generated.Farm - -// Validate validates farm object -func (f *Farm) Validate() error { - if !farmNamePattern.MatchString(f.Name) { - return fmt.Errorf("invalid farm name. name can only contain alphanumeric characters dash (-) or underscore (_)") - } - - if f.ThreebotId == 0 { - return fmt.Errorf("threebot_id is required") - } - - if len(f.WalletAddresses) == 0 { - return fmt.Errorf("invalid wallet_addresses, is required") - } - - if config.Config.Network != "" { - found := false - for _, a := range f.WalletAddresses { - validator, err := stellar.NewAddressValidator(config.Config.Network, a.Asset) - if err != nil { - if errors.Is(err, stellar.ErrAssetCodeNotSupported) { - continue - } - return errors.Wrap(err, "address validation failed") - } - - found = true - if err := validator.Valid(a.Address); err != nil { - return err - } - } - - if !found { - return errors.New("no wallet found with supported asset") - } - } - - return nil -} - -// FarmQuery helper to parse query string -type FarmQuery struct { - FarmName string - OwnerID int64 -} - -// Parse querystring from request -func (f *FarmQuery) Parse(r *http.Request) mw.Response { - var err error - f.OwnerID, err = models.QueryInt(r, "owner") - if err != nil { - return mw.BadRequest(errors.Wrap(err, "owner should be a integer")) - } - f.FarmName = r.FormValue("name") - return nil -} - -// FarmFilter type -type FarmFilter bson.D - -// WithID filter farm with ID -func (f FarmFilter) WithID(id schema.ID) FarmFilter { - return append(f, bson.E{Key: "_id", Value: id}) -} - -// WithName filter farm with name -func (f FarmFilter) WithName(name string) FarmFilter { - return append(f, bson.E{Key: "name", Value: name}) -} - -// WithOwner filter farm by owner ID -func (f FarmFilter) WithOwner(tid int64) FarmFilter { - return append(f, bson.E{Key: "threebot_id", Value: tid}) -} - -// WithFarmQuery filter based on FarmQuery -func (f FarmFilter) WithFarmQuery(q FarmQuery) FarmFilter { - if len(q.FarmName) != 0 { - f = f.WithName(q.FarmName) - } - if q.OwnerID != 0 { - f = f.WithOwner(q.OwnerID) - } - return f - -} - -// Find run the filter and return a cursor result -func (f FarmFilter) Find(ctx context.Context, db *mongo.Database, opts ...*options.FindOptions) (*mongo.Cursor, error) { - col := db.Collection(FarmCollection) - if f == nil { - f = FarmFilter{} - } - return col.Find(ctx, f, opts...) -} - -// Count number of documents matching -func (f FarmFilter) Count(ctx context.Context, db *mongo.Database) (int64, error) { - col := db.Collection(NodeCollection) - if f == nil { - f = FarmFilter{} - } - - return col.CountDocuments(ctx, f) -} - -// Get one farm that matches the filter -func (f FarmFilter) Get(ctx context.Context, db *mongo.Database) (farm Farm, err error) { - if f == nil { - f = FarmFilter{} - } - col := db.Collection(FarmCollection) - result := col.FindOne(ctx, f, options.FindOne()) - - err = result.Err() - if err != nil { - return - } - - err = result.Decode(&farm) - return -} - -// Delete deletes one farm that match the filter -func (f FarmFilter) Delete(ctx context.Context, db *mongo.Database) (err error) { - if f == nil { - f = FarmFilter{} - } - col := db.Collection(FarmCollection) - _, err = col.DeleteOne(ctx, f, options.Delete()) - return err -} - -// FarmCreate creates a new farm -func FarmCreate(ctx context.Context, db *mongo.Database, farm Farm) (schema.ID, error) { - if err := farm.Validate(); err != nil { - return 0, err - } - - col := db.Collection(FarmCollection) - id, err := models.NextID(ctx, db, FarmCollection) - if err != nil { - return id, err - } - - farm.ID = id - _, err = col.InsertOne(ctx, farm) - return id, err -} - -// FarmUpdate update an existing farm -func FarmUpdate(ctx context.Context, db *mongo.Database, id schema.ID, farm Farm) error { - farm.ID = id - - if err := farm.Validate(); err != nil { - return err - } - - col := db.Collection(FarmCollection) - f := FarmFilter{}.WithID(id) - _, err := col.UpdateOne(ctx, f, bson.M{"$set": farm}) - return err -} diff --git a/tools/explorer/pkg/directory/types/node.go b/tools/explorer/pkg/directory/types/node.go deleted file mode 100644 index fbb739a2f..000000000 --- a/tools/explorer/pkg/directory/types/node.go +++ /dev/null @@ -1,346 +0,0 @@ -package types - -import ( - "context" - "encoding/hex" - "fmt" - "time" - - "github.com/jbenet/go-base58" - "github.com/pkg/errors" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models" - "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" - generated "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const ( - // NodeCollection db collection name - NodeCollection = "node" -) - -// Node model -type Node generated.Node - -// Validate node -func (n *Node) Validate() error { - if len(n.NodeId) == 0 { - return fmt.Errorf("node_is is required") - } - - if n.FarmId == 0 { - return fmt.Errorf("farm_id is required") - } - - if len(n.OsVersion) == 0 { - return fmt.Errorf("os_version is required") - } - - if len(n.PublicKeyHex) == 0 { - return fmt.Errorf("public_key_hex is required") - } - - pk, err := hex.DecodeString(n.PublicKeyHex) - if err != nil { - return errors.Wrap(err, "fail to decode public key") - } - - if n.NodeId != base58.Encode(pk) { - return fmt.Errorf("nodeID and public key does not match") - } - - // Unfortunately, jsx schema does not support nil types - // so this is the only way to check if values are not set - empty := generated.Location{} - if n.Location == empty { - return fmt.Errorf("location is required") - } - - return nil -} - -// NodeFilter type -type NodeFilter bson.D - -// WithID filter node with ID -func (f NodeFilter) WithID(id schema.ID) NodeFilter { - return append(f, bson.E{Key: "_id", Value: id}) -} - -// WithNodeID search nodes with this node id -func (f NodeFilter) WithNodeID(id string) NodeFilter { - return append(f, bson.E{Key: "node_id", Value: id}) -} - -// WithFarmID search nodes with given farmID -func (f NodeFilter) WithFarmID(id schema.ID) NodeFilter { - return append(f, bson.E{Key: "farm_id", Value: id}) -} - -// WithTotalCap filter with total cap only units that > 0 are used -// in the query -func (f NodeFilter) WithTotalCap(cru, mru, hru, sru int64) NodeFilter { - for k, v := range map[string]int64{ - "total_resources.cru": cru, - "total_resources.mru": mru, - "total_resources.hru": hru, - "total_resources.sru": sru} { - if v > 0 { - f = append(f, bson.E{Key: k, Value: bson.M{"$gte": v}}) - } - } - - return f -} - -// WithLocation search the nodes that are located in country and or city -func (f NodeFilter) WithLocation(country, city string) NodeFilter { - if country != "" { - f = append(f, bson.E{Key: "location.country", Value: country}) - } - if city != "" { - f = append(f, bson.E{Key: "location.city", Value: city}) - } - - return f -} - -// Find run the filter and return a cursor result -func (f NodeFilter) Find(ctx context.Context, db *mongo.Database, opts ...*options.FindOptions) (*mongo.Cursor, error) { - col := db.Collection(NodeCollection) - if f == nil { - f = NodeFilter{} - } - - return col.Find(ctx, f, opts...) -} - -// Get one farm that matches the filter -func (f NodeFilter) Get(ctx context.Context, db *mongo.Database, includeproofs bool) (node Node, err error) { - if f == nil { - f = NodeFilter{} - } - - col := db.Collection(NodeCollection) - - var projection bson.D - if !includeproofs { - projection = bson.D{ - {Key: "proofs", Value: 0}, - } - } else { - projection = bson.D{} - } - result := col.FindOne(ctx, f, options.FindOne().SetProjection(projection)) - - err = result.Err() - if err != nil { - return - } - - err = result.Decode(&node) - return -} - -// Count number of documents matching -func (f NodeFilter) Count(ctx context.Context, db *mongo.Database) (int64, error) { - col := db.Collection(NodeCollection) - if f == nil { - f = NodeFilter{} - } - - return col.CountDocuments(ctx, f) -} - -// Delete deletes a node by ID -func (f NodeFilter) Delete(ctx context.Context, db *mongo.Database) error { - col := db.Collection(NodeCollection) - if f == nil { - f = NodeFilter{} - } - _, err := col.DeleteOne(ctx, f, options.Delete()) - return err -} - -// NodeCreate creates a new farm -func NodeCreate(ctx context.Context, db *mongo.Database, node Node) (schema.ID, error) { - if err := node.Validate(); err != nil { - return 0, err - } - - var farmFilter FarmFilter - farmFilter = farmFilter.WithID(schema.ID(node.FarmId)) - _, err := farmFilter.Get(ctx, db) - if err != nil { - return 0, errors.Wrap(err, "unknown farm id") - } - - var filter NodeFilter - filter = filter.WithNodeID(node.NodeId) - var id schema.ID - current, err := filter.Get(ctx, db, false) - if err != nil { - //TODO: check that this is a NOT FOUND error - id, err = models.NextID(ctx, db, NodeCollection) - if err != nil { - return id, err - } - node.Created = schema.Date{Time: time.Now()} - } else { - id = current.ID - // make sure we do NOT overwrite these field - node.Created = current.Created - node.FreeToUse = current.FreeToUse - } - - node.ID = id - if node.Proofs == nil { - node.Proofs = make([]generated.Proof, 0) - } - - node.Updated = schema.Date{Time: time.Now()} - col := db.Collection(NodeCollection) - _, err = col.UpdateOne(ctx, filter, bson.M{"$set": node}, options.Update().SetUpsert(true)) - return id, err -} - -func nodeUpdate(ctx context.Context, db *mongo.Database, nodeID string, value interface{}) error { - if nodeID == "" { - return fmt.Errorf("invalid node id") - } - - col := db.Collection(NodeCollection) - var filter NodeFilter - filter = filter.WithNodeID(nodeID) - _, err := col.UpdateOne(ctx, filter, bson.M{ - "$set": value, - }) - - return err -} - -// NodeUpdateTotalResources sets the node total resources -func NodeUpdateTotalResources(ctx context.Context, db *mongo.Database, nodeID string, capacity generated.ResourceAmount) error { - return nodeUpdate(ctx, db, nodeID, bson.M{"total_resources": capacity}) -} - -// NodeUpdateReservedResources sets the node reserved resources -func NodeUpdateReservedResources(ctx context.Context, db *mongo.Database, nodeID string, capacity generated.ResourceAmount) error { - return nodeUpdate(ctx, db, nodeID, bson.M{"reserved_resources": capacity}) -} - -// NodeUpdateUsedResources sets the node total resources -func NodeUpdateUsedResources(ctx context.Context, db *mongo.Database, nodeID string, capacity generated.ResourceAmount) error { - return nodeUpdate(ctx, db, nodeID, bson.M{"used_resources": capacity}) -} - -// NodeUpdateWorkloadsAmount sets the node reserved resources -func NodeUpdateWorkloadsAmount(ctx context.Context, db *mongo.Database, nodeID string, workloads generated.WorkloadAmount) error { - return nodeUpdate(ctx, db, nodeID, bson.M{"workloads": workloads}) -} - -// NodeUpdateUptime updates node uptime -func NodeUpdateUptime(ctx context.Context, db *mongo.Database, nodeID string, uptime int64) error { - return nodeUpdate(ctx, db, nodeID, bson.M{ - "uptime": uptime, - "updated": schema.Date{Time: time.Now()}, - }) -} - -// NodeSetInterfaces updates node interfaces -func NodeSetInterfaces(ctx context.Context, db *mongo.Database, nodeID string, ifaces []generated.Iface) error { - return nodeUpdate(ctx, db, nodeID, bson.M{ - "ifaces": ifaces, - }) -} - -// NodeSetPublicConfig sets node public config -func NodeSetPublicConfig(ctx context.Context, db *mongo.Database, nodeID string, cfg generated.PublicIface) error { - return nodeUpdate(ctx, db, nodeID, bson.M{ - "public_config": cfg, - }) -} - -// NodeUpdateFreeToUse sets node free to use flag -func NodeUpdateFreeToUse(ctx context.Context, db *mongo.Database, nodeID string, freeToUse bool) error { - return nodeUpdate(ctx, db, nodeID, bson.M{ - "free_to_use": freeToUse, - }) -} - -// NodeSetWGPorts update wireguard ports -func NodeSetWGPorts(ctx context.Context, db *mongo.Database, nodeID string, ports []uint) error { - return nodeUpdate(ctx, db, nodeID, bson.M{ - "wg_ports": ports, - }) -} - -// NodePushProof push proof to node -func NodePushProof(ctx context.Context, db *mongo.Database, nodeID string, proof generated.Proof) error { - if nodeID == "" { - return fmt.Errorf("invalid node id") - } - - col := db.Collection(NodeCollection) - var filter NodeFilter - filter = filter.WithNodeID(nodeID) - _, err := col.UpdateOne(ctx, filter, bson.M{ - "$addToSet": bson.M{ - "proofs": proof, - }, - }) - - return err -} - -// FarmsForNodes return farm objects given node ids -func FarmsForNodes(ctx context.Context, db *mongo.Database, nodeID ...string) (farms []directory.Farm, err error) { - // this pipline finds all farms from a list of nodes and return the list - if len(nodeID) == 0 { - return - } - - pipeline := mongo.Pipeline{ - {{ - Key: "$match", - Value: bson.M{ - "node_id": bson.M{ - "$in": nodeID, - }, - }, - }}, - {{ - Key: "$group", - Value: bson.M{ - "_id": "$farm_id", - }, - }}, - {{ - Key: "$lookup", - Value: bson.M{ - "from": FarmCollection, - "localField": "_id", - "foreignField": "_id", - "as": "farm"}, - }}, - {{ - Key: "$replaceRoot", - Value: bson.M{ - "newRoot": bson.M{ - "$arrayElemAt": []interface{}{"$farm", 0}, - }, - }, - }}, - } - - cur, err := db.Collection(NodeCollection).Aggregate(ctx, pipeline) - if err != nil { - return nil, err - } - - err = cur.All(ctx, &farms) - return -} diff --git a/tools/explorer/pkg/directory/types/setup.go b/tools/explorer/pkg/directory/types/setup.go deleted file mode 100644 index 7e13042ad..000000000 --- a/tools/explorer/pkg/directory/types/setup.go +++ /dev/null @@ -1,54 +0,0 @@ -package types - -import ( - "context" - "fmt" - - "github.com/rs/zerolog/log" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -// Setup sets up indexes for types, must be called at least -// Onetime during the life time of the object -func Setup(ctx context.Context, db *mongo.Database) error { - farm := db.Collection(FarmCollection) - _, err := farm.Indexes().CreateMany(ctx, []mongo.IndexModel{ - { - Keys: bson.M{"name": 1}, - Options: options.Index().SetUnique(true), - }, - }) - if err != nil { - log.Error().Err(err).Msg("failed to initialize farm index") - } - - node := db.Collection(NodeCollection) - - nodeIdexes := []mongo.IndexModel{ - { - Keys: bson.M{"node_id": 1}, - Options: options.Index().SetUnique(true), - }, - { - Keys: bson.M{"farm_id": 1}, - }, - } - - for _, x := range []string{"total_resources", "user_resources", "reserved_resources"} { - for _, y := range []string{"cru", "mru", "hru", "sru"} { - nodeIdexes = append(nodeIdexes, mongo.IndexModel{ - Keys: bson.M{fmt.Sprintf("%s.%s", x, y): 1}, - }) - } - } - - _, err = node.Indexes().CreateMany(ctx, nodeIdexes) - - if err != nil { - log.Error().Err(err).Msg("failed to initialize node index") - } - - return err -} diff --git a/tools/explorer/pkg/escrow/escrow.go b/tools/explorer/pkg/escrow/escrow.go deleted file mode 100644 index e2411799f..000000000 --- a/tools/explorer/pkg/escrow/escrow.go +++ /dev/null @@ -1,52 +0,0 @@ -package escrow - -import ( - "context" - - "github.com/pkg/errors" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" - "github.com/threefoldtech/zos/tools/explorer/pkg/escrow/types" - workloadstypes "github.com/threefoldtech/zos/tools/explorer/pkg/workloads/types" - "go.mongodb.org/mongo-driver/mongo" -) - -// Escrow are responsible for the payment flow of a reservation -type Escrow interface { - Run(ctx context.Context) error - RegisterReservation(reservation workloads.Reservation, supportedCurrencies []string) (types.CustomerEscrowInformation, error) - ReservationDeployed(reservationID schema.ID) - ReservationCanceled(reservationID schema.ID) -} - -// Free implements the Escrow interface in a way that makes all reservation free -type Free struct { - db *mongo.Database -} - -// NewFree creates a new EscrowFree object -func NewFree(db *mongo.Database) *Free { return &Free{db: db} } - -// Run implements the escrow interface -func (e *Free) Run(ctx context.Context) error { - return nil -} - -// RegisterReservation implements the escrow interface -func (e *Free) RegisterReservation(reservation workloads.Reservation, _ []string) (detail types.CustomerEscrowInformation, err error) { - - if reservation.NextAction == workloads.NextActionPay { - if err = workloadstypes.ReservationSetNextAction(context.Background(), e.db, reservation.ID, workloads.NextActionDeploy); err != nil { - err = errors.Wrapf(err, "failed to change state of reservation %d to DEPLOY", reservation.ID) - return - } - } - - return detail, nil -} - -// ReservationDeployed implements the escrow interface -func (e *Free) ReservationDeployed(reservationID schema.ID) {} - -// ReservationCanceled implements the escrow interface -func (e *Free) ReservationCanceled(reservationID schema.ID) {} diff --git a/tools/explorer/pkg/escrow/payout_distribution.go b/tools/explorer/pkg/escrow/payout_distribution.go deleted file mode 100644 index ada9492cc..000000000 --- a/tools/explorer/pkg/escrow/payout_distribution.go +++ /dev/null @@ -1,34 +0,0 @@ -package escrow - -import ( - "fmt" - - "github.com/threefoldtech/zos/tools/explorer/pkg/stellar" -) - -// assetDistributions for currently supported tokens -var assetDistributions = map[stellar.Asset]payoutDistribution{ - // It's a bit annoying that we don't differntiate between mainnet and standard here, - // load all assets - stellar.TFTMainnet: {farmer: 90, burned: 0, foundation: 10}, - stellar.TFTTestnet: {farmer: 90, burned: 0, foundation: 10}, - stellar.FreeTFTMainnet: {farmer: 0, burned: 100, foundation: 0}, - stellar.FreeTFTTestnet: {farmer: 0, burned: 100, foundation: 0}, -} - -// payoutDistribution for a reservation. This represents the percentage of the -// reservation cost that goes to each party. -type payoutDistribution struct { - farmer uint8 - burned uint8 - foundation uint8 -} - -func (p payoutDistribution) validate() error { - totalAmount := p.farmer + p.burned + p.foundation - if totalAmount != 100 { - return fmt.Errorf("expected total payout distribution to be 100, got %d", totalAmount) - } - - return nil -} diff --git a/tools/explorer/pkg/escrow/payout_distribution_test.go b/tools/explorer/pkg/escrow/payout_distribution_test.go deleted file mode 100644 index 5c69d7361..000000000 --- a/tools/explorer/pkg/escrow/payout_distribution_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package escrow - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPayoutDistributionValidation(t *testing.T) { - distributions := []payoutDistribution{ - { - farmer: 23, - burned: 56, - foundation: 0, - }, - { - farmer: 33, - burned: 33, - foundation: 33, - }, - { - farmer: 50, - burned: 40, - foundation: 10, - }, - } - - assert.Error(t, distributions[0].validate(), "") - assert.Error(t, distributions[1].validate(), "") - assert.NoError(t, distributions[2].validate()) -} - -func TestKnownPayoutDistributions(t *testing.T) { - for _, pd := range assetDistributions { - assert.NoError(t, pd.validate()) - } -} diff --git a/tools/explorer/pkg/escrow/resource.go b/tools/explorer/pkg/escrow/resource.go deleted file mode 100644 index c1cff48db..000000000 --- a/tools/explorer/pkg/escrow/resource.go +++ /dev/null @@ -1,171 +0,0 @@ -package escrow - -import ( - "math" - "math/big" - - "github.com/pkg/errors" - "github.com/stellar/go/amount" - "github.com/stellar/go/xdr" - "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" -) - -type ( - rsuPerFarmer map[int64]rsu - - rsuPerNode map[string]rsu - - rsu struct { - cru int64 - sru int64 - hru int64 - mru float64 - } - - cloudUnits struct { - cu float64 - su float64 - } -) - -// cost price of cloud units: -// - $10 for a compute unit -// - $8 for a storage unit -// TFT price is fixed at $0.15 / TFT -// since this means neither compute unit nor cloud unit returns a nice value when -// expressed in TFT, we fix this to 3 digit precision. -const ( - computeUnitTFTCost = 66.667 // 10 / 0.15 - storageUnitTFTCost = 53.334 // 10 / 0.15 -) - -// calculateReservationCost calculates the cost of reservation based on a resource per farmer map -func (e Stellar) calculateReservationCost(rsuPerFarmerMap rsuPerFarmer) (map[int64]xdr.Int64, error) { - cloudUnitsPerFarmer := make(map[int64]cloudUnits) - for id, rsu := range rsuPerFarmerMap { - cloudUnitsPerFarmer[id] = rsuToCu(rsu) - } - costPerFarmerMap := make(map[int64]xdr.Int64) - for id, cu := range cloudUnitsPerFarmer { - // stellar does not have a nice type for currency, so use big.Float's during - // calculation to avoid floating point errors. Since both the price and - // cloud units are 3 digit precision floats, the result will be at most a 6 - // digit precision float. Stellar has 7 digits precision, so we can use this - // result as is. - // TODO: do we round this to 3 digits precision as well? - // NOTE: yes we need 3 big.Floats for the final calculation, or it screws up - total := big.NewFloat(0) - a := big.NewFloat(0) - b := big.NewFloat(0) - total = total.Add( - a.Mul(big.NewFloat(computeUnitTFTCost), big.NewFloat(cu.cu)), - b.Mul(big.NewFloat(storageUnitTFTCost), big.NewFloat(cu.su)), - ) - cost, err := amount.Parse(total.String()) - if err != nil { - return nil, errors.Wrap(err, "could not parse calculated cost") - } - costPerFarmerMap[id] = cost - } - return costPerFarmerMap, nil -} - -func (e Stellar) processReservationResources(resData workloads.ReservationData) (rsuPerFarmer, error) { - rsuPerNodeMap := make(rsuPerNode) - for _, cont := range resData.Containers { - rsuPerNodeMap[cont.NodeId] = rsuPerNodeMap[cont.NodeId].add(processContainer(cont)) - } - for _, vol := range resData.Volumes { - rsuPerNodeMap[vol.NodeId] = rsuPerNodeMap[vol.NodeId].add(processVolume(vol)) - } - for _, zdb := range resData.Zdbs { - rsuPerNodeMap[zdb.NodeId] = rsuPerNodeMap[zdb.NodeId].add(processZdb(zdb)) - } - for _, k8s := range resData.Kubernetes { - rsuPerNodeMap[k8s.NodeId] = rsuPerNodeMap[k8s.NodeId].add(processKubernetes(k8s)) - } - rsuPerFarmerMap := make(rsuPerFarmer) - for nodeID, rsu := range rsuPerNodeMap { - node, err := e.nodeAPI.Get(e.ctx, e.db, nodeID, false) - if err != nil { - return nil, errors.Wrap(err, "could not get node") - } - rsuPerFarmerMap[node.FarmId] = rsuPerFarmerMap[node.FarmId].add(rsu) - } - return rsuPerFarmerMap, nil -} - -func processContainer(cont workloads.Container) rsu { - return rsu{ - cru: cont.Capacity.Cpu, - // round mru to 4 digits precision - mru: math.Round(float64(cont.Capacity.Memory)/1024*10000) / 10000, - } -} - -func processVolume(vol workloads.Volume) rsu { - switch vol.Type { - case workloads.VolumeTypeHDD: - return rsu{ - hru: vol.Size, - } - case workloads.VolumeTypeSSD: - return rsu{ - sru: vol.Size, - } - } - return rsu{} -} - -func processZdb(zdb workloads.ZDB) rsu { - switch zdb.DiskType { - case workloads.DiskTypeHDD: - return rsu{ - hru: zdb.Size, - } - case workloads.DiskTypeSSD: - return rsu{ - sru: zdb.Size, - } - } - return rsu{} - -} - -func processKubernetes(k8s workloads.K8S) rsu { - switch k8s.Size { - case 1: - return rsu{ - cru: 1, - mru: 2, - sru: 50, - } - case 2: - return rsu{ - cru: 2, - mru: 4, - sru: 100, - } - } - return rsu{} - -} - -func (r rsu) add(other rsu) rsu { - return rsu{ - cru: r.cru + other.cru, - sru: r.sru + other.sru, - hru: r.hru + other.hru, - mru: r.mru + other.mru, - } -} - -// rsuToCu converts resource units to cloud units. Cloud units are rounded to 3 -// decimal places -func rsuToCu(r rsu) cloudUnits { - cloudUnits := cloudUnits{ - cu: math.Round(math.Min(r.mru/4*0.95, float64(r.cru)*2)*1000) / 1000, - su: math.Round((float64(r.hru)/1093+float64(r.sru)/91)*1000) / 1000, - } - return cloudUnits -} diff --git a/tools/explorer/pkg/escrow/resource_test.go b/tools/explorer/pkg/escrow/resource_test.go deleted file mode 100644 index e41cfe45b..000000000 --- a/tools/explorer/pkg/escrow/resource_test.go +++ /dev/null @@ -1,595 +0,0 @@ -package escrow - -import ( - "context" - "strconv" - "testing" - - "github.com/pkg/errors" - "github.com/stellar/go/xdr" - "github.com/stretchr/testify/assert" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" - directorytypes "github.com/threefoldtech/zos/tools/explorer/pkg/directory/types" - "github.com/threefoldtech/zos/tools/explorer/pkg/stellar" - "go.mongodb.org/mongo-driver/mongo" -) - -type ( - nodeAPIMock struct{} -) - -const precision = 1e7 - -func TestProcessReservation(t *testing.T) { - data := workloads.ReservationData{ - Containers: []workloads.Container{ - { - NodeId: "1", - Capacity: workloads.ContainerCapacity{ - Cpu: 2, - Memory: 4096, - }, - }, - { - NodeId: "1", - Capacity: workloads.ContainerCapacity{ - Cpu: 1, - Memory: 2096, - }, - }, - { - NodeId: "2", - Capacity: workloads.ContainerCapacity{ - Cpu: 3, - Memory: 3096, - }, - }, - { - NodeId: "2", - Capacity: workloads.ContainerCapacity{ - Cpu: 2, - Memory: 5000, - }, - }, - { - NodeId: "3", - Capacity: workloads.ContainerCapacity{ - Cpu: 2, - Memory: 6589, - }, - }, - { - NodeId: "3", - Capacity: workloads.ContainerCapacity{ - Cpu: 2, - Memory: 1234, - }, - }, - }, - Volumes: []workloads.Volume{ - { - NodeId: "1", - Type: workloads.VolumeTypeHDD, - Size: 500, - }, - { - NodeId: "1", - Type: workloads.VolumeTypeHDD, - Size: 500, - }, - { - NodeId: "2", - Type: workloads.VolumeTypeSSD, - Size: 100, - }, - { - NodeId: "2", - Type: workloads.VolumeTypeHDD, - Size: 2500, - }, - { - NodeId: "3", - Type: workloads.VolumeTypeHDD, - Size: 1000, - }, - }, - Zdbs: []workloads.ZDB{ - { - NodeId: "1", - DiskType: workloads.DiskTypeSSD, - Size: 750, - }, - { - NodeId: "3", - DiskType: workloads.DiskTypeSSD, - Size: 250, - }, - { - NodeId: "3", - DiskType: workloads.DiskTypeHDD, - Size: 500, - }, - }, - Kubernetes: []workloads.K8S{ - { - NodeId: "1", - Size: 1, - }, - { - NodeId: "1", - Size: 2, - }, - { - NodeId: "1", - Size: 2, - }, - { - NodeId: "2", - Size: 2, - }, - { - NodeId: "2", - Size: 2, - }, - { - NodeId: "3", - Size: 2, - }, - }, - } - - escrow := Stellar{ - wallet: &stellar.Wallet{}, - db: nil, - reservationChannel: nil, - nodeAPI: &nodeAPIMock{}, - } - - farmRsu, err := escrow.processReservationResources(data) - if err != nil { - t.Fatal(err) - } - - if len(farmRsu) != 3 { - t.Errorf("Found %d farmers, expected to find 3", len(farmRsu)) - } - - // check farm tid 1 - rsu := farmRsu[1] - if rsu.cru != 8 { - t.Errorf("Farmer 1 total cru is %d, expected 8", rsu.cru) - } - if rsu.mru != 16.0469 { - t.Errorf("Farmer 1 total mru is %f, expected 10", rsu.mru) - } - if rsu.sru != 1000 { - t.Errorf("Farmer 1 total sru is %d, expected 1000", rsu.sru) - } - if rsu.hru != 1000 { - t.Errorf("Farmer 1 total hru is %d, expected 1000", rsu.hru) - } - - // check farm tid 2 - rsu = farmRsu[2] - if rsu.cru != 9 { - t.Errorf("Farmer 2 total cru is %d, expected 9", rsu.cru) - } - if rsu.mru != 15.9062 { - t.Errorf("Farmer 2 total mru is %f, expected 8", rsu.mru) - } - if rsu.sru != 300 { - t.Errorf("Farmer 2 total sru is %d, expected 300", rsu.sru) - } - if rsu.hru != 2500 { - t.Errorf("Farmer 2 total hru is %d, expected 2500", rsu.hru) - } - - // check farm tid 3 - rsu = farmRsu[3] - if rsu.cru != 6 { - t.Errorf("Farmer 3 total cru is %d, expected 6", rsu.cru) - } - if rsu.mru != 11.6397 { - t.Errorf("Farmer 3 total mru is %f, expected 4", rsu.mru) - } - if rsu.sru != 350 { - t.Errorf("Farmer 3 total sru is %d, expected 350", rsu.sru) - } - if rsu.hru != 1500 { - t.Errorf("Farmer 3 total hru is %d, expected 1500", rsu.hru) - } -} - -func TestProcessContainer(t *testing.T) { - cont := workloads.Container{ - Capacity: workloads.ContainerCapacity{ - Cpu: 1, - Memory: 1024, - }, - } - rsu := processContainer(cont) - - if rsu.cru != 1 { - t.Errorf("Processed volume cru is %d, expected 1", rsu.cru) - } - if rsu.mru != 1 { - t.Errorf("Processed volume mru is %f, expected 1", rsu.mru) - } - if rsu.sru != 0 { - t.Errorf("Processed volume sru is %d, expected 0", rsu.sru) - } - if rsu.hru != 0 { - t.Errorf("Processed volume hru is %d, expected 0", rsu.hru) - } - - cont = workloads.Container{ - Capacity: workloads.ContainerCapacity{ - Cpu: 4, - Memory: 2024, - }, - } - rsu = processContainer(cont) - - if rsu.cru != 4 { - t.Errorf("Processed volume cru is %d, expected 1", rsu.cru) - } - if rsu.mru != 1.9766 { - t.Errorf("Processed volume mru is %f, expected 1", rsu.mru) - } - if rsu.sru != 0 { - t.Errorf("Processed volume sru is %d, expected 0", rsu.sru) - } - if rsu.hru != 0 { - t.Errorf("Processed volume hru is %d, expected 0", rsu.hru) - } -} - -func TestProcessVolume(t *testing.T) { - testSize := int64(27) // can be random number - - vol := workloads.Volume{ - Size: testSize, - Type: workloads.VolumeTypeHDD, - } - rsu := processVolume(vol) - - if rsu.cru != 0 { - t.Errorf("Processed volume cru is %d, expected 0", rsu.cru) - } - if rsu.mru != 0 { - t.Errorf("Processed volume mru is %f, expected 0", rsu.mru) - } - if rsu.sru != 0 { - t.Errorf("Processed volume sru is %d, expected 0", rsu.sru) - } - if rsu.hru != testSize { - t.Errorf("Processed volume hru is %d, expected %d", rsu.hru, testSize) - } - - vol = workloads.Volume{ - Size: testSize, - Type: workloads.VolumeTypeSSD, - } - rsu = processVolume(vol) - - if rsu.cru != 0 { - t.Errorf("Processed volume cru is %d, expected 0", rsu.cru) - } - if rsu.mru != 0 { - t.Errorf("Processed volume mru is %f, expected 0", rsu.mru) - } - if rsu.sru != testSize { - t.Errorf("Processed volume sru is %d, expected %d", rsu.sru, testSize) - } - if rsu.hru != 0 { - t.Errorf("Processed volume hru is %d, expected 0", rsu.hru) - } -} - -func TestProcessZdb(t *testing.T) { - testSize := int64(43) // can be random number - - zdb := workloads.ZDB{ - DiskType: workloads.DiskTypeHDD, - Size: testSize, - } - rsu := processZdb(zdb) - - if rsu.cru != 0 { - t.Errorf("Processed zdb cru is %d, expected 0", rsu.cru) - } - if rsu.mru != 0 { - t.Errorf("Processed zdb mru is %f, expected 0", rsu.mru) - } - if rsu.sru != 0 { - t.Errorf("Processed zdb sru is %d, expected 0", rsu.sru) - } - if rsu.hru != testSize { - t.Errorf("Processed zdb hru is %d, expected %d", rsu.hru, testSize) - } - - zdb = workloads.ZDB{ - DiskType: workloads.DiskTypeSSD, - Size: testSize, - } - rsu = processZdb(zdb) - - if rsu.cru != 0 { - t.Errorf("Processed zdb cru is %d, expected 0", rsu.cru) - } - if rsu.mru != 0 { - t.Errorf("Processed zdb mru is %f, expected 0", rsu.mru) - } - if rsu.sru != testSize { - t.Errorf("Processed zdb sru is %d, expected %d", rsu.sru, testSize) - } - if rsu.hru != 0 { - t.Errorf("Processed zdb hru is %d, expected 0", rsu.hru) - } -} - -func TestProcessKubernetes(t *testing.T) { - k8s := workloads.K8S{ - Size: 1, - } - rsu := processKubernetes(k8s) - - if rsu.cru != 1 { - t.Errorf("Processed zdb cru is %d, expected 1", rsu.cru) - } - if rsu.mru != 2 { - t.Errorf("Processed zdb mru is %f, expected 2", rsu.mru) - } - if rsu.sru != 50 { - t.Errorf("Processed zdb sru is %d, expected 50", rsu.sru) - } - if rsu.hru != 0 { - t.Errorf("Processed zdb hru is %d, expected 0", rsu.hru) - } - - k8s = workloads.K8S{ - Size: 2, - } - rsu = processKubernetes(k8s) - - if rsu.cru != 2 { - t.Errorf("Processed zdb cru is %d, expected 2", rsu.cru) - } - if rsu.mru != 4 { - t.Errorf("Processed zdb mru is %f, expected 4", rsu.mru) - } - if rsu.sru != 100 { - t.Errorf("Processed zdb sru is %d, expected 100", rsu.sru) - } - if rsu.hru != 0 { - t.Errorf("Processed zdb hru is %d, expected 0", rsu.hru) - } -} - -func TestRsuAdd(t *testing.T) { - first := rsu{cru: 1, mru: 2, sru: 3, hru: 4} - second := rsu{cru: 8, mru: 6, sru: 4, hru: 2} - result := first.add(second) - - if result.cru != 9 { - t.Errorf("Result cru is %d, expected 9", result.cru) - } - if result.mru != 8 { - t.Errorf("Result mru is %f, expected 8", result.mru) - } - if result.sru != 7 { - t.Errorf("Result sru is %d, expected 7", result.sru) - } - if result.hru != 6 { - t.Errorf("Result hru is %d, expected 6", result.hru) - } -} - -func TestCalculateReservationCost(t *testing.T) { - data := workloads.ReservationData{ - Containers: []workloads.Container{ - { - NodeId: "1", - Capacity: workloads.ContainerCapacity{ - Cpu: 2, - Memory: 2048, - }, - }, - { - NodeId: "1", - Capacity: workloads.ContainerCapacity{ - Cpu: 4, - Memory: 5120, - }, - }, - { - NodeId: "3", - Capacity: workloads.ContainerCapacity{ - Cpu: 2, - Memory: 1000, - }, - }, - { - NodeId: "3", - Capacity: workloads.ContainerCapacity{ - Cpu: 4, - Memory: 4000, - }, - }, - { - NodeId: "3", - Capacity: workloads.ContainerCapacity{ - Cpu: 4, - Memory: 4096, - }, - }, - { - NodeId: "3", - Capacity: workloads.ContainerCapacity{ - Cpu: 1, - Memory: 1024, - }, - }, - }, - Volumes: []workloads.Volume{ - { - NodeId: "1", - Type: workloads.VolumeTypeHDD, - Size: 500, - }, - { - NodeId: "1", - Type: workloads.VolumeTypeHDD, - Size: 500, - }, - { - NodeId: "3", - Type: workloads.VolumeTypeSSD, - Size: 100, - }, - { - NodeId: "3", - Type: workloads.VolumeTypeHDD, - Size: 2500, - }, - { - NodeId: "3", - Type: workloads.VolumeTypeHDD, - Size: 1000, - }, - }, - Zdbs: []workloads.ZDB{ - { - NodeId: "1", - DiskType: workloads.DiskTypeSSD, - Size: 750, - }, - { - NodeId: "3", - DiskType: workloads.DiskTypeSSD, - Size: 250, - }, - { - NodeId: "3", - DiskType: workloads.DiskTypeHDD, - Size: 500, - }, - }, - Kubernetes: []workloads.K8S{ - { - NodeId: "1", - Size: 1, - }, - { - NodeId: "1", - Size: 2, - }, - { - NodeId: "1", - Size: 2, - }, - { - NodeId: "3", - Size: 2, - }, - { - NodeId: "3", - Size: 2, - }, - { - NodeId: "3", - Size: 2, - }, - }, - } - - escrow := Stellar{ - wallet: &stellar.Wallet{}, - db: nil, - reservationChannel: nil, - nodeAPI: &nodeAPIMock{}, - } - - farmRsu, err := escrow.processReservationResources(data) - assert.NoError(t, err) - - res, err := escrow.calculateReservationCost(farmRsu) - if ok := assert.NoError(t, err); !ok { - t.Fatal() - } - - assert.True(t, len(res) == 2) - // cru: 11, sru: 1000, hru: 1000, mru: 17 - // 4.037 * 66.667 + 11.904 * 53.334 - assert.Equal(t, xdr.Int64(904.022615*precision), res[1]) - // cru: 17, sru: 650, hru: 4000, mru: 21.8829 - // 5.197 * 66.667 + 10.803 * 53.334 - assert.Equal(t, xdr.Int64(922.635601*precision), res[3]) -} - -func TestResourceUnitsToCloudUnits(t *testing.T) { - rsus := []rsu{ - {}, - { - cru: 2, - mru: 4, - hru: 1093, - sru: 91, - }, - { - cru: 1, - mru: 2, - hru: 1093, - sru: 0, - }, - { - cru: 1, - mru: 8, - hru: 0, - sru: 91, - }, - { - cru: 1, - mru: 12, - hru: 1000, - sru: 250, - }, - } - - expectedCUs := []cloudUnits{ - {}, - { - cu: 0.95, - su: 2, - }, - { - cu: 0.475, - su: 1, - }, - { - cu: 1.9, - su: 1, - }, - { - cu: 2, - su: 3.662, - }, - } - - for i := range rsus { - assert.Equal(t, rsuToCu(rsus[i]), expectedCUs[i]) - } -} - -func (napim *nodeAPIMock) Get(_ context.Context, _ *mongo.Database, nodeID string, _ bool) (directorytypes.Node, error) { - idInt, err := strconv.Atoi(nodeID) - if err != nil { - return directorytypes.Node{}, errors.New("node not found") - } - return directorytypes.Node{ - ID: schema.ID(idInt), - NodeId: nodeID, - FarmId: int64(idInt), - }, nil -} diff --git a/tools/explorer/pkg/escrow/stellar.go b/tools/explorer/pkg/escrow/stellar.go deleted file mode 100644 index 4fa74c687..000000000 --- a/tools/explorer/pkg/escrow/stellar.go +++ /dev/null @@ -1,656 +0,0 @@ -package escrow - -import ( - "context" - "fmt" - "math" - "time" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - "github.com/stellar/go/xdr" - "github.com/threefoldtech/zos/pkg/schema" - gdirectory "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" - "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" - "github.com/threefoldtech/zos/tools/explorer/pkg/directory" - directorytypes "github.com/threefoldtech/zos/tools/explorer/pkg/directory/types" - "github.com/threefoldtech/zos/tools/explorer/pkg/escrow/types" - "github.com/threefoldtech/zos/tools/explorer/pkg/stellar" - workloadtypes "github.com/threefoldtech/zos/tools/explorer/pkg/workloads/types" - "go.mongodb.org/mongo-driver/mongo" -) - -type ( - // Stellar service manages a dedicate wallet for payments for reservations. - Stellar struct { - foundationAddress string - wallet *stellar.Wallet - db *mongo.Database - - reservationChannel chan reservationRegisterJob - deployedChannel chan schema.ID - cancelledChannel chan schema.ID - - nodeAPI NodeAPI - farmAPI FarmAPI - - ctx context.Context - } - - // NodeAPI operations on node database - NodeAPI interface { - // Get a node from the database using its ID - Get(ctx context.Context, db *mongo.Database, id string, proofs bool) (directorytypes.Node, error) - } - - // FarmAPI operations on farm database - FarmAPI interface { - // GetByID get a farm from the database using its ID - GetByID(ctx context.Context, db *mongo.Database, id int64) (directorytypes.Farm, error) - } - - reservationRegisterJob struct { - reservation workloads.Reservation - supportedCurrencyCodes []string - responseChan chan reservationRegisterJobResponse - } - - reservationRegisterJobResponse struct { - data types.CustomerEscrowInformation - err error - } -) - -const ( - // interval between every check of active escrow accounts - balanceCheckInterval = time.Minute * 1 -) - -const ( - // amount of digits of precision a calculated reservation cost has, at worst - costPrecision = 6 -) - -var ( - // ErrNoCurrencySupported indicates a reservation was offered but none of the currencies - // the farmer wants to pay in are currently supported - ErrNoCurrencySupported = errors.New("none of the offered currencies are currently supported") - // ErrNoCurrencyShared indicates that none of the currencies offered in the reservation - // is supported by all farmers used - ErrNoCurrencyShared = errors.New("none of the provided currencies is supported by all farmers") -) - -// NewStellar creates a new escrow object and fetches all addresses for the escrow wallet -func NewStellar(wallet *stellar.Wallet, db *mongo.Database, foundationAddress string) *Stellar { - jobChannel := make(chan reservationRegisterJob) - deployChannel := make(chan schema.ID) - cancelChannel := make(chan schema.ID) - - addr := foundationAddress - if addr == "" { - addr = wallet.PublicAddress() - } - - return &Stellar{ - wallet: wallet, - db: db, - foundationAddress: addr, - nodeAPI: &directory.NodeAPI{}, - farmAPI: &directory.FarmAPI{}, - reservationChannel: jobChannel, - deployedChannel: deployChannel, - cancelledChannel: cancelChannel, - } -} - -// Run the escrow until the context is done -func (e *Stellar) Run(ctx context.Context) error { - ticker := time.NewTicker(balanceCheckInterval) - defer ticker.Stop() - - e.ctx = ctx - - for { - select { - case <-ctx.Done(): - log.Info().Msg("escrow context done, exiting") - return nil - - case <-ticker.C: - log.Info().Msg("scanning active escrow accounts balance") - if err := e.checkReservations(); err != nil { - log.Error().Err(err).Msgf("failed to check reservations") - } - - log.Info().Msg("scanning for expired escrows") - if err := e.refundExpiredReservations(); err != nil { - log.Error().Err(err).Msgf("failed to refund expired reservations") - } - - case job := <-e.reservationChannel: - log.Info().Int64("reservation_id", int64(job.reservation.ID)).Msg("processing new reservation escrow for reservation") - details, err := e.processReservation(job.reservation, job.supportedCurrencyCodes) - if err != nil { - log.Error(). - Err(err). - Int64("reservation_id", int64(job.reservation.ID)). - Msgf("failed to check reservations") - } - job.responseChan <- reservationRegisterJobResponse{ - err: err, - data: details, - } - - case id := <-e.deployedChannel: - log.Info().Int64("reservation_id", int64(id)).Msg("trying to pay farmer for deployed reservation") - if err := e.payoutFarmers(id); err != nil { - log.Error(). - Err(err). - Int64("reservation_id", int64(id)). - Msgf("failed to payout farmers") - } - - case id := <-e.cancelledChannel: - log.Info().Int64("reservation_id", int64(id)).Msg("trying to refund clients for canceled reservation") - if err := e.refundClients(id); err != nil { - log.Error(). - Err(err). - Int64("reservation_id", int64(id)). - Msgf("could not refund clients") - } - } - } -} - -func (e *Stellar) refundExpiredReservations() error { - // load expired escrows - reservationEscrows, err := types.GetAllExpiredReservationPaymentInfos(e.ctx, e.db) - if err != nil { - return errors.Wrap(err, "failed to load active reservations from escrow") - } - for _, escrowInfo := range reservationEscrows { - log.Info().Int64("id", int64(escrowInfo.ReservationID)).Msg("expired escrow") - - if err := e.refundEscrow(escrowInfo); err != nil { - log.Error().Err(err).Msgf("failed to refund reservation escrow") - continue - } - - escrowInfo.Canceled = true - if err = types.ReservationPaymentInfoUpdate(e.ctx, e.db, escrowInfo); err != nil { - log.Error().Err(err).Msgf("failed to mark expired reservation escrow info as cancelled") - } - } - return nil -} - -// checkReservations checks all the active reservations and marks those who are funded. -// if a reservation is funded then it will mark this reservation as to DEPLOY. -// if its underfunded it will throw an error. -func (e *Stellar) checkReservations() error { - // load active escrows - reservationEscrows, err := types.GetAllActiveReservationPaymentInfos(e.ctx, e.db) - if err != nil { - return errors.Wrap(err, "failed to load active reservations from escrow") - } - - for _, escrowInfo := range reservationEscrows { - if err := e.checkReservationPaid(escrowInfo); err != nil { - log.Error(). - Str("address", escrowInfo.Address). - Int64("reservation_id", int64(escrowInfo.ReservationID)). - Err(err). - Msg("failed to check reservation escrow funding status") - continue - } - } - return nil -} - -// CheckReservationPaid verifies if an escrow account received sufficient balance -// to pay for a reservation. If this is the case, the reservation will be moved -// to the deploy state, and the escrow state will be updated to indicate that this -// escrow has indeed been paid for this reservation, so it is not checked anymore -// in the future. -func (e *Stellar) checkReservationPaid(escrowInfo types.ReservationPaymentInformation) error { - slog := log.With(). - Str("address", escrowInfo.Address). - Int64("reservation_id", int64(escrowInfo.ReservationID)). - Logger() - - // calculate total amount needed for reservation - requiredValue := xdr.Int64(0) - for _, escrowAccount := range escrowInfo.Infos { - requiredValue += escrowAccount.TotalAmount - } - - balance, _, err := e.wallet.GetBalance(escrowInfo.Address, escrowInfo.ReservationID, escrowInfo.Asset) - if err != nil { - return errors.Wrap(err, "failed to verify escrow account balance") - } - - if balance < requiredValue { - slog.Debug().Msgf("required balance %d not reached yet (%d)", requiredValue, balance) - return nil - } - - slog.Debug().Msgf("required balance %d funded (%d), continue reservation", requiredValue, balance) - - reservation, err := workloadtypes.ReservationFilter{}.WithID(escrowInfo.ReservationID).Get(e.ctx, e.db) - if err != nil { - return errors.Wrap(err, "failed to load reservation") - } - - pl, err := workloadtypes.NewPipeline(reservation) - if err != nil { - return errors.Wrap(err, "failed to process reservation pipeline") - } - - reservation, _ = pl.Next() - if !reservation.IsAny(workloadtypes.Pay) { - // Do not continue, but also take no action to drive the reservation - // as much as possible from the main explorer part. - slog.Warn().Msg("reservation is paid, but no longer in pay state") - // We warn because this is an unusual state to be in, yet there are - // situations where this could happen. For example, we load the escrow, - // the explorer then invalidates the actual reservation (e.g. user cancels), - // we then load the updated reservation, which is no longer in pay state, - // but the explorer is still cancelling the escrow, so we get here. As stated - // above, we drive the escrow as much as possible from the workloads, with the - // timeouts coming from the escrow itself, so this situation should always - // resole itself. If we notice this log is coming back periodically, it thus means - // there is a bug somewhere else in the code. - // As a result, this state is therefore not considered an error. - return nil - } - - slog.Info().Msg("all farmer are paid, trying to move to deploy state") - - // update reservation - if err = workloadtypes.ReservationSetNextAction(e.ctx, e.db, escrowInfo.ReservationID, workloadtypes.Deploy); err != nil { - return errors.Wrap(err, "failed to set reservation to DEPLOY state") - } - - escrowInfo.Paid = true - if err = types.ReservationPaymentInfoUpdate(e.ctx, e.db, escrowInfo); err != nil { - return errors.Wrap(err, "failed to mark reservation escrow info as paid") - } - - slog.Debug().Msg("escrow marked as paid") - - return nil -} - -// processReservation processes a single reservation -// calculates resources and their costs -func (e *Stellar) processReservation(reservation workloads.Reservation, offeredCurrencyCodes []string) (types.CustomerEscrowInformation, error) { - var customerInfo types.CustomerEscrowInformation - - // filter out unsupported currencies - currencies := []stellar.Asset{} - for _, offeredCurrency := range offeredCurrencyCodes { - asset, err := e.wallet.AssetFromCode(offeredCurrency) - if err != nil { - if err == stellar.ErrAssetCodeNotSupported { - continue - } - return customerInfo, err - } - // Sanity check - if _, exists := assetDistributions[asset]; !exists { - // no payout distribution info set, log error and treat as if the asset - // is not supported - log.Error().Msgf("asset %s supported by wallet but no payout distribution found in escrow", asset) - continue - } - currencies = append(currencies, asset) - } - - if len(currencies) == 0 { - return customerInfo, ErrNoCurrencySupported - } - - rsuPerFarmer, err := e.processReservationResources(reservation.DataReservation) - if err != nil { - return customerInfo, errors.Wrap(err, "failed to process reservation resources") - } - - // check which currencies are accepted by all farmers - // the farm ids have conveniently been provided when checking the used rsu - farmIDs := make([]int64, 0, len(rsuPerFarmer)) - var asset stellar.Asset - for _, currency := range currencies { - // if the farmer does not receive anything in the first place, they always - // all agree on this currency - if assetDistributions[currency].farmer == 0 { - asset = currency - break - } - // check if all used farms have an address for this asset set up - supported, err := e.checkAssetSupport(farmIDs, asset) - if err != nil { - return customerInfo, errors.Wrap(err, "could not verify asset support") - } - if supported { - asset = currency - break - } - } - - if asset == "" { - return customerInfo, ErrNoCurrencyShared - } - - res, err := e.calculateReservationCost(rsuPerFarmer) - if err != nil { - return customerInfo, errors.Wrap(err, "failed to process reservation resources costs") - } - - address, err := e.createOrLoadAccount(reservation.CustomerTid) - if err != nil { - return customerInfo, errors.Wrap(err, "failed to get escrow address for customer") - } - - details := make([]types.EscrowDetail, 0, len(res)) - for farmer, value := range res { - details = append(details, types.EscrowDetail{ - FarmerID: schema.ID(farmer), - TotalAmount: value, - }) - } - reservationPaymentInfo := types.ReservationPaymentInformation{ - Infos: details, - Address: address, - ReservationID: reservation.ID, - Expiration: reservation.DataReservation.ExpirationProvisioning, - Asset: asset, - Paid: false, - Canceled: false, - Released: false, - } - err = types.ReservationPaymentInfoCreate(e.ctx, e.db, reservationPaymentInfo) - if err != nil { - return customerInfo, errors.Wrap(err, "failed to create reservation payment information") - } - log.Info().Int64("id", int64(reservation.ID)).Msg("processed reservation and created payment information") - customerInfo.Address = address - customerInfo.Asset = asset - customerInfo.Details = details - return customerInfo, nil -} - -// refundClients refunds clients if the reservation is cancelled -func (e *Stellar) refundClients(id schema.ID) error { - rpi, err := types.ReservationPaymentInfoGet(e.ctx, e.db, id) - if err != nil { - return errors.Wrap(err, "failed to get reservation escrow info") - } - if rpi.Released || rpi.Canceled { - // already paid - return nil - } - if err := e.refundEscrow(rpi); err != nil { - log.Error().Err(err).Msg("failed to refund escrow") - return errors.Wrap(err, "could not refund escrow") - } - rpi.Canceled = true - if err = types.ReservationPaymentInfoUpdate(e.ctx, e.db, rpi); err != nil { - return errors.Wrapf(err, "could not mark escrows for %d as canceled", rpi.ReservationID) - } - log.Debug().Int64("id", int64(rpi.ReservationID)).Msg("refunded clients for reservation") - return nil -} - -// payoutFarmers pays out the farmer for a processed reservation -func (e *Stellar) payoutFarmers(id schema.ID) error { - rpi, err := types.ReservationPaymentInfoGet(e.ctx, e.db, id) - if err != nil { - return errors.Wrap(err, "failed to get reservation escrow info") - } - if rpi.Released || rpi.Canceled { - // already paid - return nil - } - - paymentDistribution, exists := assetDistributions[rpi.Asset] - if !exists { - return fmt.Errorf("no payment distribution found for asset %s", rpi.Asset) - } - - // keep track of total amount to burn and to send to foundation - var toBurn, toFoundation xdr.Int64 - - // collect the farmer addresses and amount they should receive, we already - // have sufficient balance on the escrow to cover this - paymentInfo := make([]stellar.PayoutInfo, 0, len(rpi.Infos)) - - for _, escrowDetails := range rpi.Infos { - farmerAmount, burnAmount, foundationAmount := e.splitPayout(escrowDetails.TotalAmount, paymentDistribution) - toBurn += burnAmount - toFoundation += foundationAmount - - if farmerAmount > 0 { - // in case of an error in this flow we continue, so we try to pay as much - // farmers as possible even if one fails - farm, err := e.farmAPI.GetByID(e.ctx, e.db, int64(escrowDetails.FarmerID)) - if err != nil { - log.Error().Msgf("failed to load farm info: %s", err) - continue - } - - destination, err := addressByAsset(farm.WalletAddresses, rpi.Asset) - if err != nil { - // FIXME: this is probably not ok, what do we do in this case ? - log.Error().Err(err).Msgf("failed to find address for %s for farmer %d", rpi.Asset.Code(), farm.ID) - continue - } - - // farmerAmount can't be pooled so add an info immediately - paymentInfo = append(paymentInfo, - stellar.PayoutInfo{ - Address: destination, - Amount: farmerAmount, - }, - ) - } - } - - // a burn is a transfer of tokens back to the issuer - if toBurn > 0 { - paymentInfo = append(paymentInfo, - stellar.PayoutInfo{ - Address: rpi.Asset.Issuer(), - Amount: toBurn, - }) - } - - // ship remainder to the foundation - if toFoundation > 0 { - paymentInfo = append(paymentInfo, - stellar.PayoutInfo{ - Address: e.foundationAddress, - Amount: toFoundation, - }) - } - - addressInfo, err := types.CustomerAddressByAddress(e.ctx, e.db, rpi.Address) - if err != nil { - log.Error().Msgf("failed to load escrow address info: %s", err) - return errors.Wrap(err, "could not load escrow address info") - } - if err = e.wallet.PayoutFarmers(addressInfo.Secret, paymentInfo, id, rpi.Asset); err != nil { - log.Error().Msgf("failed to pay farmer: %s for reservation %d", err, id) - return errors.Wrap(err, "could not pay farmer") - } - // now refund any possible overpayment - if err = e.wallet.Refund(addressInfo.Secret, id, rpi.Asset); err != nil { - log.Error().Msgf("failed to refund overpayment farmer: %s", err) - return errors.Wrap(err, "could not refund overpayment") - } - log.Info(). - Str("escrow address", rpi.Address). - Int64("reservation id", int64(rpi.ReservationID)). - Msgf("paid farmer") - - rpi.Released = true - if err = types.ReservationPaymentInfoUpdate(e.ctx, e.db, rpi); err != nil { - return errors.Wrapf(err, "could not mark escrows for %d as released", rpi.ReservationID) - } - return nil -} - -func (e *Stellar) refundEscrow(escrowInfo types.ReservationPaymentInformation) error { - slog := log.With(). - Str("address", escrowInfo.Address). - Int64("reservation_id", int64(escrowInfo.ReservationID)). - Logger() - - slog.Info().Msgf("try to refund client for escrow") - - addressInfo, err := types.CustomerAddressByAddress(e.ctx, e.db, escrowInfo.Address) - if err != nil { - return errors.Wrap(err, "failed to load escrow info") - } - - if err = e.wallet.Refund(addressInfo.Secret, escrowInfo.ReservationID, escrowInfo.Asset); err != nil { - return errors.Wrap(err, "failed to refund clients") - } - - slog.Info().Msgf("refunded client for escrow") - return nil -} - -// RegisterReservation registers a workload reservation -func (e *Stellar) RegisterReservation(reservation workloads.Reservation, supportedCurrencies []string) (types.CustomerEscrowInformation, error) { - job := reservationRegisterJob{ - reservation: reservation, - supportedCurrencyCodes: supportedCurrencies, - responseChan: make(chan reservationRegisterJobResponse), - } - e.reservationChannel <- job - - response := <-job.responseChan - - return response.data, response.err -} - -// ReservationDeployed informs the escrow that a reservation has been successfully -// deployed, so the escrow can release the funds to the farmer (and refund any excess) -func (e *Stellar) ReservationDeployed(reservationID schema.ID) { - e.deployedChannel <- reservationID -} - -// ReservationCanceled informs the escrow of a canceled reservation so it can refund -// the user -func (e *Stellar) ReservationCanceled(reservationID schema.ID) { - e.cancelledChannel <- reservationID -} - -// createOrLoadAccount creates or loads account based on customer id -func (e *Stellar) createOrLoadAccount(customerTID int64) (string, error) { - res, err := types.CustomerAddressGet(context.Background(), e.db, customerTID) - if err != nil { - if err == types.ErrAddressNotFound { - seed, address, err := e.wallet.CreateAccount() - if err != nil { - return "", errors.Wrapf(err, "failed to create a new account for customer %d", customerTID) - } - err = types.CustomerAddressCreate(context.Background(), e.db, types.CustomerAddress{ - CustomerTID: customerTID, - Address: address, - Secret: seed, - }) - if err != nil { - return "", errors.Wrapf(err, "failed to save a new account for customer %d", customerTID) - } - log.Debug(). - Int64("customer", int64(customerTID)). - Str("address", address). - Msgf("created new escrow address for customer") - return address, nil - } - return "", errors.Wrap(err, "failed to get customer address") - } - log.Debug(). - Int64("customer", int64(customerTID)). - Str("address", res.Address). - Msgf("escrow address found for customer") - - return res.Address, nil -} - -// splitPayout to a farmer in the amount the farmer receives, the amount to be burned, -// and the amount the foundation receives -func (e *Stellar) splitPayout(totalAmount xdr.Int64, distribution payoutDistribution) (xdr.Int64, xdr.Int64, xdr.Int64) { - // we can't just use big.Float for this calculation, since we need to verify - // the rounding afterwards - - // calculate missing precision digits, to perform percentage division without - // floating point operations - requiredPrecision := 2 + costPrecision - missingPrecision := requiredPrecision - e.wallet.PrecisionDigits() - - multiplier := int64(1) - if missingPrecision > 0 { - multiplier = int64(math.Pow10(missingPrecision)) - } - - amount := int64(totalAmount) * multiplier - - baseAmount := amount / 100 - farmerAmount := baseAmount * int64(distribution.farmer) - burnAmount := baseAmount * int64(distribution.burned) - foundationAmount := baseAmount * int64(distribution.foundation) - - // collect parts which will be missing in division, if any - var change int64 - change += farmerAmount % multiplier - change += burnAmount % multiplier - change += foundationAmount % multiplier - - // change is now necessarily a multiple of multiplier - change /= multiplier - // we tracked all change which would be removed by the following integer - // devisions - farmerAmount /= multiplier - burnAmount /= multiplier - foundationAmount /= multiplier - - // give change to whichever gets funds anyway, in the following order: - // - farmer - // - burned - // - foundation - if farmerAmount != 0 { - farmerAmount += change - } else if burnAmount != 0 { - burnAmount += change - } else if foundationAmount != 0 { - foundationAmount += change - } - - return xdr.Int64(farmerAmount), xdr.Int64(burnAmount), xdr.Int64(foundationAmount) -} - -// checkAssetSupport for all unique farms in the reservation -func (e *Stellar) checkAssetSupport(farmIDs []int64, asset stellar.Asset) (bool, error) { - for _, id := range farmIDs { - farm, err := e.farmAPI.GetByID(e.ctx, e.db, id) - if err != nil { - return false, errors.Wrap(err, "could not load farm") - } - if _, err := addressByAsset(farm.WalletAddresses, asset); err != nil { - // this only errors if the asset is not present - return false, nil - } - } - return true, nil -} - -func addressByAsset(addrs []gdirectory.WalletAddress, asset stellar.Asset) (string, error) { - for _, a := range addrs { - if a.Asset == asset.Code() && a.Address != "" { - return a.Address, nil - } - } - return "", fmt.Errorf("not address found for asset %s", asset) -} diff --git a/tools/explorer/pkg/escrow/stellar_test.go b/tools/explorer/pkg/escrow/stellar_test.go deleted file mode 100644 index 61bc35cdf..000000000 --- a/tools/explorer/pkg/escrow/stellar_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package escrow - -import ( - "testing" - - "github.com/stellar/go/xdr" - "github.com/stretchr/testify/assert" - "github.com/threefoldtech/zos/tools/explorer/pkg/stellar" -) - -func TestPayoutDistribution(t *testing.T) { - // for these tests, keep in mind that the `amount` given is in the highest - // precision of the underlying wallet, but the reservation costs only have - // up to 6 digits precision. In case of stellar, the wallet has 7 digits precision. - // This means the smallest amount that will be expressed is `10` rather than `1`. - // - // note that the actual amount to be paid can have up to the wallets precision, - // i.e. it is possible to have greater than 6 digits precision - pds := []payoutDistribution{ - { - farmer: 50, - burned: 50, - foundation: 0, - }, - { - farmer: 34, - burned: 33, - foundation: 33, - }, - { - farmer: 40, - burned: 40, - foundation: 20, - }, - { - farmer: 0, - burned: 73, - foundation: 27, - }, - } - - for _, pd := range pds { - assert.NoError(t, pd.validate()) - } - - w, err := stellar.New("", stellar.NetworkTest, nil) - assert.NoError(t, err) - - e := NewStellar(w, nil, "") - - // check rounding in some trivial cases - farmer, burn, fd := e.splitPayout(10, pds[0]) - assert.Equal(t, xdr.Int64(5), farmer) - assert.Equal(t, xdr.Int64(5), burn) - assert.Equal(t, xdr.Int64(0), fd) - - farmer, burn, fd = e.splitPayout(10, pds[1]) - assert.Equal(t, xdr.Int64(4), farmer) - assert.Equal(t, xdr.Int64(3), burn) - assert.Equal(t, xdr.Int64(3), fd) - - farmer, burn, fd = e.splitPayout(10, pds[2]) - assert.Equal(t, xdr.Int64(4), farmer) - assert.Equal(t, xdr.Int64(4), burn) - assert.Equal(t, xdr.Int64(2), fd) - - farmer, burn, fd = e.splitPayout(10, pds[3]) - assert.Equal(t, xdr.Int64(0), farmer) - assert.Equal(t, xdr.Int64(8), burn) - assert.Equal(t, xdr.Int64(2), fd) - - farmer, burn, fd = e.splitPayout(330, pds[0]) - assert.Equal(t, xdr.Int64(165), farmer) - assert.Equal(t, xdr.Int64(165), burn) - assert.Equal(t, xdr.Int64(0), fd) - - farmer, burn, fd = e.splitPayout(330, pds[1]) - assert.Equal(t, xdr.Int64(114), farmer) - assert.Equal(t, xdr.Int64(108), burn) - assert.Equal(t, xdr.Int64(108), fd) - - farmer, burn, fd = e.splitPayout(330, pds[2]) - assert.Equal(t, xdr.Int64(132), farmer) - assert.Equal(t, xdr.Int64(132), burn) - assert.Equal(t, xdr.Int64(66), fd) - - farmer, burn, fd = e.splitPayout(330, pds[3]) - assert.Equal(t, xdr.Int64(0), farmer) - assert.Equal(t, xdr.Int64(241), burn) - assert.Equal(t, xdr.Int64(89), fd) -} diff --git a/tools/explorer/pkg/escrow/types/address.go b/tools/explorer/pkg/escrow/types/address.go deleted file mode 100644 index 7b41e5ce2..000000000 --- a/tools/explorer/pkg/escrow/types/address.go +++ /dev/null @@ -1,69 +0,0 @@ -package types - -import ( - "context" - - "github.com/pkg/errors" - - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -const ( - // AddressCollection db collection name - AddressCollection = "addresses" -) - -var ( - // ErrAddressExists error for non existing doc - ErrAddressExists = errors.New("address(s) for farmer customer already exists") - // ErrAddressNotFound error for not found - ErrAddressNotFound = errors.New("address information not found") -) - -type ( - // CustomerAddress holds the customer escrow address and key - CustomerAddress struct { - CustomerTID int64 `bson:"customer_tid" json:"customer_tid"` - Address string `bson:"address" json:"address"` - Secret string `bson:"secret" json:"secret"` - } -) - -// CustomerAddressCreate creates the reservation payment information -func CustomerAddressCreate(ctx context.Context, db *mongo.Database, cAddress CustomerAddress) error { - col := db.Collection(AddressCollection) - _, err := col.InsertOne(ctx, cAddress) - if err != nil { - if merr, ok := err.(mongo.WriteException); ok { - errCode := merr.WriteErrors[0].Code - if errCode == 11000 { - return ErrAddressExists - } - } - return err - } - return nil -} - -// CustomerAddressGet one address bycustomerTID -func CustomerAddressGet(ctx context.Context, db *mongo.Database, customerTID int64) (CustomerAddress, error) { - var customerAddress CustomerAddress - doc := db.Collection(AddressCollection).FindOne(ctx, bson.M{"customer_tid": customerTID}) - if errors.Is(doc.Err(), mongo.ErrNoDocuments) { - return CustomerAddress{}, ErrAddressNotFound - } - err := doc.Decode(&customerAddress) - return customerAddress, err -} - -// CustomerAddressByAddress gets one address using the address -func CustomerAddressByAddress(ctx context.Context, db *mongo.Database, address string) (CustomerAddress, error) { - var customerAddress CustomerAddress - doc := db.Collection(AddressCollection).FindOne(ctx, bson.M{"address": address}) - if errors.Is(doc.Err(), mongo.ErrNoDocuments) { - return CustomerAddress{}, ErrAddressNotFound - } - err := doc.Decode(&customerAddress) - return customerAddress, err -} diff --git a/tools/explorer/pkg/escrow/types/escrow.go b/tools/explorer/pkg/escrow/types/escrow.go deleted file mode 100644 index 45dbb7c4d..000000000 --- a/tools/explorer/pkg/escrow/types/escrow.go +++ /dev/null @@ -1,141 +0,0 @@ -package types - -import ( - "context" - "time" - - "github.com/pkg/errors" - "github.com/stellar/go/xdr" - - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/pkg/stellar" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -const ( - // EscrowCollection db collection name - EscrowCollection = "escrow" -) - -var ( - // ErrEscrowExists is returned when trying to save escrow information for a - // reservation that already has escrow information - ErrEscrowExists = errors.New("escrow(s) for reservation already exists") - // ErrEscrowNotFound is returned if escrow information is not found - ErrEscrowNotFound = errors.New("escrow information not found") -) - -type ( - // ReservationPaymentInformation stores the reservation payment information - ReservationPaymentInformation struct { - ReservationID schema.ID `bson:"_id"` - Address string `bson:"address"` - Expiration schema.Date `bson:"expiration"` - Asset stellar.Asset `bson:"asset"` - Infos []EscrowDetail `bson:"infos"` - // Paid indicates the reservation escrows have been fully funded, and - // the reservation has been moved from the "PAY" state to the "DEPLOY" - // state - Paid bool `bson:"paid"` - // Released indicates the reservation has been fully deployed, and - // that an attempt was made to pay the farmers. If this flag is set, it is - // still possible that there are funds on an escrow related to this transaction - // either because someone funded it after the reservation was already - // deployed , because there was an error paying the farmers - // or because there was an error refunding any overpaid amount. - Released bool `bson:"released"` - // Canceled indicates that the reservation was canceled, either by the - // user, or because a workload deployment failed, which resulted in the - // entire reservation being canceled. As a result, an attempt was made - // to refund the client. It is possible for this to have failed. - Canceled bool `bson:"canceled"` - } - - // EscrowDetail hold the details of an escrow address - EscrowDetail struct { - FarmerID schema.ID `bson:"farmer_id" json:"farmer_id"` - TotalAmount xdr.Int64 `bson:"total_amount" json:"total_amount"` - } - - // CustomerEscrowInformation is the escrow information which will get exposed - // to the customer once he creates a reservation - CustomerEscrowInformation struct { - Address string `json:"address"` - Asset stellar.Asset `json:"asset"` - Details []EscrowDetail `json:"details"` - } -) - -// ReservationPaymentInfoCreate creates the reservation payment information -func ReservationPaymentInfoCreate(ctx context.Context, db *mongo.Database, reservationPaymentInfo ReservationPaymentInformation) error { - col := db.Collection(EscrowCollection) - _, err := col.InsertOne(ctx, reservationPaymentInfo) - if err != nil { - if merr, ok := err.(mongo.WriteException); ok { - errCode := merr.WriteErrors[0].Code - if errCode == 11000 { - return ErrEscrowExists - } - } - return err - } - return nil -} - -// ReservationPaymentInfoUpdate update reservation payment info -func ReservationPaymentInfoUpdate(ctx context.Context, db *mongo.Database, update ReservationPaymentInformation) error { - filter := bson.M{"_id": update.ReservationID} - // actually update the user with final data - if _, err := db.Collection(EscrowCollection).UpdateOne(ctx, filter, bson.M{"$set": update}); err != nil { - return err - } - - return nil -} - -// ReservationPaymentInfoGet a single reservation escrow info using its id -func ReservationPaymentInfoGet(ctx context.Context, db *mongo.Database, id schema.ID) (ReservationPaymentInformation, error) { - col := db.Collection(EscrowCollection) - var rpi ReservationPaymentInformation - res := col.FindOne(ctx, bson.M{"_id": id}) - if err := res.Err(); err != nil { - if err == mongo.ErrNoDocuments { - return rpi, ErrEscrowNotFound - } - return rpi, err - } - err := res.Decode(&rpi) - return rpi, err - -} - -// GetAllActiveReservationPaymentInfos get all active reservation payment information -func GetAllActiveReservationPaymentInfos(ctx context.Context, db *mongo.Database) ([]ReservationPaymentInformation, error) { - filter := bson.M{"paid": false, "expiration": bson.M{"$gt": schema.Date{Time: time.Now()}}} - cursor, err := db.Collection(EscrowCollection).Find(ctx, filter) - if err != nil { - return nil, errors.Wrap(err, "failed to get cursor over active payment infos") - } - paymentInfos := make([]ReservationPaymentInformation, 0) - err = cursor.All(ctx, &paymentInfos) - if err != nil { - err = errors.Wrap(err, "failed to decode active payment information") - } - return paymentInfos, err -} - -// GetAllExpiredReservationPaymentInfos get all active reservation payment information -func GetAllExpiredReservationPaymentInfos(ctx context.Context, db *mongo.Database) ([]ReservationPaymentInformation, error) { - filter := bson.M{"released": false, "canceled": false, "expiration": bson.M{"$lte": schema.Date{Time: time.Now()}}} - cursor, err := db.Collection(EscrowCollection).Find(ctx, filter) - if err != nil { - return nil, errors.Wrap(err, "failed to get cursor over expired payment infos") - } - paymentInfos := make([]ReservationPaymentInformation, 0) - err = cursor.All(ctx, &paymentInfos) - if err != nil { - err = errors.Wrap(err, "failed to decode expired payment information") - } - return paymentInfos, err -} diff --git a/tools/explorer/pkg/escrow/types/setup.go b/tools/explorer/pkg/escrow/types/setup.go deleted file mode 100644 index 923e4d4a4..000000000 --- a/tools/explorer/pkg/escrow/types/setup.go +++ /dev/null @@ -1,42 +0,0 @@ -package types - -import ( - "context" - - "github.com/rs/zerolog/log" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -// Setup sets up indexes for types, must be called at least -// Onetime during the life time of the object -func Setup(ctx context.Context, db *mongo.Database) error { - escrow := db.Collection(EscrowCollection) - _, err := escrow.Indexes().CreateMany(ctx, []mongo.IndexModel{ - { - Keys: bson.M{"_id": 1}, - }, - }) - if err != nil { - log.Error().Err(err).Msg("failed to initialize reservation payment index") - return err - } - - addresses := db.Collection(AddressCollection) - _, err = addresses.Indexes().CreateMany(ctx, []mongo.IndexModel{ - { - Keys: bson.M{"customer_tid": 1}, - Options: options.Index().SetUnique(true), - }, - { - Keys: bson.M{"address": 1}, - Options: options.Index().SetUnique(true), - }, - }) - if err != nil { - log.Error().Err(err).Msg("failed to initialize reservation payment index") - } - - return err -} diff --git a/tools/explorer/pkg/phonebook/setup.go b/tools/explorer/pkg/phonebook/setup.go deleted file mode 100644 index bea36fae9..000000000 --- a/tools/explorer/pkg/phonebook/setup.go +++ /dev/null @@ -1,29 +0,0 @@ -package phonebook - -import ( - "context" - "net/http" - - "github.com/gorilla/mux" - "github.com/threefoldtech/zos/tools/explorer/mw" - phonebook "github.com/threefoldtech/zos/tools/explorer/pkg/phonebook/types" - "go.mongodb.org/mongo-driver/mongo" -) - -// Setup injects and initializes directory package -func Setup(parent *mux.Router, db *mongo.Database) error { - if err := phonebook.Setup(context.TODO(), db); err != nil { - return err - } - - var userAPI UserAPI - users := parent.PathPrefix("/users").Subrouter() - - users.HandleFunc("", mw.AsHandlerFunc(userAPI.create)).Methods(http.MethodPost).Name("user-create") - users.HandleFunc("", mw.AsHandlerFunc(userAPI.list)).Methods(http.MethodGet).Name(("user-list")) - users.HandleFunc("/{user_id}", mw.AsHandlerFunc(userAPI.register)).Methods(http.MethodPut).Name("user-register") - users.HandleFunc("/{user_id}", mw.AsHandlerFunc(userAPI.get)).Methods(http.MethodGet).Name("user-get") - users.HandleFunc("/{user_id}/validate", mw.AsHandlerFunc(userAPI.validate)).Methods(http.MethodPost).Name("user-validate") - - return nil -} diff --git a/tools/explorer/pkg/phonebook/types/setup.go b/tools/explorer/pkg/phonebook/types/setup.go deleted file mode 100644 index 321efe08f..000000000 --- a/tools/explorer/pkg/phonebook/types/setup.go +++ /dev/null @@ -1,31 +0,0 @@ -package types - -import ( - "context" - - "github.com/rs/zerolog/log" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -// Setup sets up indexes for types, must be called at least -// Onetime during the life time of the object -func Setup(ctx context.Context, db *mongo.Database) error { - farm := db.Collection(UserCollection) - _, err := farm.Indexes().CreateMany(ctx, []mongo.IndexModel{ - { - Keys: bson.M{"name": 1}, - Options: options.Index().SetUnique(true), - }, - { - Keys: bson.M{"email": 1}, - Options: options.Index().SetUnique(true), - }, - }) - if err != nil { - log.Error().Err(err).Msg("failed to initialize user index") - } - - return err -} diff --git a/tools/explorer/pkg/phonebook/types/user.go b/tools/explorer/pkg/phonebook/types/user.go deleted file mode 100644 index fa30e17d4..000000000 --- a/tools/explorer/pkg/phonebook/types/user.go +++ /dev/null @@ -1,240 +0,0 @@ -package types - -import ( - "bytes" - "context" - "fmt" - "strings" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - - "github.com/threefoldtech/zos/pkg/crypto" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models" - generated "github.com/threefoldtech/zos/tools/explorer/models/generated/phonebook" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const ( - // UserCollection db collection name - UserCollection = "user" -) - -var ( - // ErrUserExists returned if user with same name exists - ErrUserExists = errors.New("user with same name or email exists") - // ErrUserNotFound is returned if user is not found - ErrUserNotFound = errors.New("user not found") - // ErrBadUserUpdate is returned when invalid data is passed to update - ErrBadUserUpdate = errors.New("bad data during user update") - // ErrAuthorization returned if user is not allowed to do an operation - ErrAuthorization = errors.New("operation not allowed") -) - -// User type -type User generated.User - -// Validate makes the sanity check requires for the user type -func (u User) Validate() error { - if strings.ToLower(u.Name) != u.Name { - return fmt.Errorf("name should be all lower case") - } - if strings.ToLower(u.Email) != u.Email { - return fmt.Errorf("email should be all lower case") - } - - if len(u.Name) < 3 { - return fmt.Errorf("name should be at least 3 character") - } - - return nil -} - -// Encode user data for signing -func (u *User) Encode() []byte { - var buf bytes.Buffer - // order is encoding is very important - // from jumpscale, we see that the fields - // are encoding like - // id, name, email, ip-addr, description, pubkey - buf.WriteString(fmt.Sprint(int64(u.ID))) - buf.WriteString(u.Name) - buf.WriteString(u.Email) - buf.WriteString(u.Host) - buf.WriteString(u.Description) - buf.WriteString(u.Pubkey) - - return buf.Bytes() -} - -// UserFilter type -type UserFilter bson.D - -// WithID filters user with ID -func (f UserFilter) WithID(id schema.ID) UserFilter { - if id == 0 { - return f - } - return append(f, bson.E{Key: "_id", Value: id}) -} - -// WithName filters user with name -func (f UserFilter) WithName(name string) UserFilter { - if name == "" { - return f - } - return append(f, bson.E{Key: "name", Value: name}) -} - -// WithEmail filters user with email -func (f UserFilter) WithEmail(email string) UserFilter { - if email == "" { - return f - } - return append(f, bson.E{Key: "email", Value: email}) -} - -// Find all users that matches filter -func (f UserFilter) Find(ctx context.Context, db *mongo.Database, opts ...*options.FindOptions) (*mongo.Cursor, error) { - if f == nil { - f = UserFilter{} - } - return db.Collection(UserCollection).Find(ctx, f, opts...) -} - -// Count number of documents matching -func (f UserFilter) Count(ctx context.Context, db *mongo.Database) (int64, error) { - col := db.Collection(UserCollection) - if f == nil { - f = UserFilter{} - } - - return col.CountDocuments(ctx, f) -} - -// Get single user -func (f UserFilter) Get(ctx context.Context, db *mongo.Database) (user User, err error) { - if f == nil { - f = UserFilter{} - } - - result := db.Collection(UserCollection).FindOne(ctx, f, options.FindOne()) - if err = result.Err(); err != nil { - return - } - - err = result.Decode(&user) - return -} - -// UserCreate creates the user -func UserCreate(ctx context.Context, db *mongo.Database, name, email, pubkey string) (user User, err error) { - if len(name) == 0 { - return user, fmt.Errorf("invalid name, can't be empty") - } - - if _, err := crypto.KeyFromHex(pubkey); err != nil { - return user, errors.Wrapf(err, "invalid public key %s", pubkey) - } - - var filter UserFilter - filter = filter.WithName(name) - _, err = filter.Get(ctx, db) - - if err == nil { - return user, ErrUserExists - } else if err != nil && !errors.Is(err, mongo.ErrNoDocuments) { - return user, err - } - // else ErrNoDocuments - - id := models.MustID(ctx, db, UserCollection) - user = User{ - ID: id, - Name: name, - Email: email, - Pubkey: pubkey, - } - - col := db.Collection(UserCollection) - _, err = col.InsertOne(ctx, user) - if err != nil { - if merr, ok := err.(mongo.WriteException); ok { - errCode := merr.WriteErrors[0].Code - if errCode == 11000 { - return user, ErrUserExists - } - } - return user, err - } - return -} - -// UserUpdate update user info -func UserUpdate(ctx context.Context, db *mongo.Database, id schema.ID, signature []byte, update User) error { - update.ID = id - - // then we find the user that matches this given ID - var filter UserFilter - filter = filter.WithID(id) - - current, err := filter.Get(ctx, db) - if err != nil && errors.Is(err, mongo.ErrNoDocuments) { - return ErrUserNotFound - } - - // user need to always sign with current stored public key - // even to update new key - key, err := crypto.KeyFromHex(current.Pubkey) - if err != nil { - return err - } - - // NOTE: verification here is done over the update request - // data. We make sure that the signature is indeed done - // with the priv key part of the user - encoded := update.Encode() - log.Debug().Str("encoded", string(encoded)).Msg("encoded message") - if err := crypto.Verify(key, encoded, signature); err != nil { - return errors.Wrap(ErrBadUserUpdate, "payload verification failed") - } - - // if public key update is required, we make sure - // that is valid key. - if len(update.Pubkey) != 0 { - _, err := crypto.KeyFromHex(update.Pubkey) - if err != nil { - return errors.Wrap(ErrBadUserUpdate, "invalid public key") - } - - current.Pubkey = update.Pubkey - } - - // sanity check make sure user is not trying to update his name - if len(update.Name) != 0 && current.Name != update.Name { - return errors.Wrap(ErrBadUserUpdate, "can not update name") - } - - // copy all modified fields. - if len(update.Email) != 0 { - current.Email = update.Email - } - - if len(update.Description) != 0 { - current.Description = update.Description - } - - if len(update.Host) != 0 { - current.Host = update.Host - } - - // actually update the user with final data - if _, err := db.Collection(UserCollection).UpdateOne(ctx, filter, bson.M{"$set": current}); err != nil { - return err - } - - return nil -} diff --git a/tools/explorer/pkg/phonebook/types/user_test.go b/tools/explorer/pkg/phonebook/types/user_test.go deleted file mode 100644 index abcad1e74..000000000 --- a/tools/explorer/pkg/phonebook/types/user_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package types - -import ( - "testing" - - "gotest.tools/assert" -) - -func TestUser_Validate(t *testing.T) { - tests := []struct { - name string - u User - err string - }{ - { - name: "user.3bot", - u: User{ - Name: "user.3bot", - }, - err: "", - }, - { - name: "User.3bot", - u: User{ - Name: "User.3bot", - }, - err: "name should be all lower case", - }, - { - name: "lower.email", - u: User{ - Name: "com", - Email: "user@example.com", - }, - err: "", - }, - { - name: "upper.email", - u: User{ - Name: "com", - Email: "User@example.com", - }, - err: "email should be all lower case", - }, - { - name: "ab", - u: User{ - Name: "ab", - }, - err: "name should be at least 3 character", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.u.Validate() - if tt.err != "" { - assert.Error(t, err, tt.err) - } else { - assert.NilError(t, err) - } - }) - } -} diff --git a/tools/explorer/pkg/phonebook/user.go b/tools/explorer/pkg/phonebook/user.go deleted file mode 100644 index fa9635100..000000000 --- a/tools/explorer/pkg/phonebook/user.go +++ /dev/null @@ -1,200 +0,0 @@ -package phonebook - -import ( - "crypto/ed25519" - "encoding/hex" - "encoding/json" - "fmt" - "math" - "net/http" - "strconv" - - "github.com/gorilla/mux" - "github.com/pkg/errors" - "github.com/threefoldtech/zos/pkg/crypto" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models" - "github.com/threefoldtech/zos/tools/explorer/mw" - "github.com/threefoldtech/zos/tools/explorer/pkg/phonebook/types" -) - -// UserAPI struct -type UserAPI struct{} - -// create user entry point, makes sure name is free for reservation -func (u *UserAPI) create(r *http.Request) (interface{}, mw.Response) { - var user types.User - - defer r.Body.Close() - - if err := json.NewDecoder(r.Body).Decode(&user); err != nil { - return nil, mw.BadRequest(err) - } - - // https://github.com/threefoldtech/zos/issues/706 - if err := user.Validate(); err != nil { - return nil, mw.BadRequest(err) - } - - db := mw.Database(r) - user, err := types.UserCreate(r.Context(), db, user.Name, user.Email, user.Pubkey) - if err != nil && errors.Is(err, types.ErrUserExists) { - return nil, mw.Conflict(err) - } else if err != nil { - return nil, mw.Error(err) - } - - return user, mw.Created() -} - -/* -register -As implemented in threebot. It works as a USER update function. To update -any fields, you need to make sure your payload has an extra "sender_signature_hex" -field that is the signature of the payload using the user private key. - -This signature is done on a message that is built as defined by the User.Encode() method -*/ -func (u *UserAPI) register(r *http.Request) (interface{}, mw.Response) { - id, err := u.parseID(mux.Vars(r)["user_id"]) - if err != nil { - return nil, mw.BadRequest(errors.Wrap(err, "invalid user id")) - } - - var payload struct { - types.User - Signature string `json:"sender_signature_hex"` // because why not `signature`! - } - - defer r.Body.Close() - - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - return nil, mw.BadRequest(err) - } - - if len(payload.Signature) == 0 { - return nil, mw.BadRequest(fmt.Errorf("signature is required")) - } - - signature, err := hex.DecodeString(payload.Signature) - if err != nil { - return nil, mw.BadRequest(errors.Wrap(err, "invalid signature hex")) - } - db := mw.Database(r) - - if err := types.UserUpdate(r.Context(), db, schema.ID(id), signature, payload.User); err != nil { - if errors.Is(err, types.ErrBadUserUpdate) { - return nil, mw.BadRequest(err) - } - return nil, mw.Error(err) - } - - return nil, nil -} - -func (u *UserAPI) list(r *http.Request) (interface{}, mw.Response) { - var filter types.UserFilter - filter = filter.WithName(r.FormValue("name")) - filter = filter.WithEmail(r.FormValue("email")) - - db := mw.Database(r) - pager := models.PageFromRequest(r) - cur, err := filter.Find(r.Context(), db, pager) - if err != nil { - return nil, mw.Error(err) - } - - users := []types.User{} - if err := cur.All(r.Context(), &users); err != nil { - return nil, mw.Error(err) - } - - total, err := filter.Count(r.Context(), db) - if err != nil { - return nil, mw.Error(err, http.StatusInternalServerError) - } - - nrPages := math.Ceil(float64(total) / float64(*pager.Limit)) - pages := fmt.Sprintf("%d", int64(nrPages)) - - return users, mw.Ok().WithHeader("Pages", pages) -} - -func (u *UserAPI) parseID(id string) (schema.ID, error) { - v, err := strconv.ParseInt(id, 10, 64) - if err != nil { - return 0, errors.Wrap(err, "invalid id format") - } - - return schema.ID(v), nil -} - -func (u *UserAPI) get(r *http.Request) (interface{}, mw.Response) { - - userID, err := u.parseID(mux.Vars(r)["user_id"]) - if err != nil { - return nil, mw.BadRequest(err) - } - var filter types.UserFilter - filter = filter.WithID(userID) - - db := mw.Database(r) - user, err := filter.Get(r.Context(), db) - if err != nil { - return nil, mw.NotFound(err) - } - - return user, nil -} - -func (u *UserAPI) validate(r *http.Request) (interface{}, mw.Response) { - var payload struct { - Payload string `json:"payload"` - Signature string `json:"signature"` - } - - userID, err := u.parseID(mux.Vars(r)["user_id"]) - if err != nil { - return nil, mw.BadRequest(err) - } - - defer r.Body.Close() - - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - return nil, mw.BadRequest(err) - } - - data, err := hex.DecodeString(payload.Payload) - if err != nil { - return nil, mw.BadRequest(errors.Wrap(err, "payload must be hex encoded string of original data")) - } - - signature, err := hex.DecodeString(payload.Signature) - if err != nil { - return nil, mw.BadRequest(errors.Wrap(err, "signature must be hex encoded string of original data")) - } - - var filter types.UserFilter - filter = filter.WithID(userID) - - db := mw.Database(r) - user, err := filter.Get(r.Context(), db) - if err != nil { - return nil, mw.NotFound(err) - } - - key, err := crypto.KeyFromHex(user.Pubkey) - if err != nil { - return nil, mw.Error(err) - } - - if len(key) != ed25519.PublicKeySize { - return nil, mw.Error(fmt.Errorf("public key has the wrong size")) - } - - return struct { - IsValid bool `json:"is_valid"` - }{ - IsValid: ed25519.Verify(key, data, signature), - }, nil -} diff --git a/tools/explorer/pkg/phonebook/user_test.go b/tools/explorer/pkg/phonebook/user_test.go deleted file mode 100644 index fac45545c..000000000 --- a/tools/explorer/pkg/phonebook/user_test.go +++ /dev/null @@ -1 +0,0 @@ -package phonebook diff --git a/tools/explorer/pkg/stellar/asset.go b/tools/explorer/pkg/stellar/asset.go deleted file mode 100644 index f70eb8099..000000000 --- a/tools/explorer/pkg/stellar/asset.go +++ /dev/null @@ -1,63 +0,0 @@ -package stellar - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" -) - -// Asset on the stellar network, both code and issuer in the form : -type Asset string - -// Supported assets for the wallet. Assets are different based on testnet/mainnet -const ( - TFTMainnet Asset = "TFT:GBOVQKJYHXRR3DX6NOX2RRYFRCUMSADGDESTDNBDS6CDVLGVESRTAC47" - TFTTestnet Asset = "TFT:GA47YZA3PKFUZMPLQ3B5F2E3CJIB57TGGU7SPCQT2WAEYKN766PWIMB3" - - FreeTFTMainnet Asset = "FreeTFT:GCBGS5TFE2BPPUVY55ZPEMWWGR6CLQ7T6P46SOFGHXEBJ34MSP6HVEUT" - FreeTFTTestnet Asset = "FreeTFT:GBLDUINEFYTF7XEE7YNWA3JQS4K2VD37YU7I2YAE7R5AHZDKQXSS2J6R" -) - -// internal vars to set up the wallet with supported assets -var ( - mainnetAssets = map[Asset]struct{}{ - TFTMainnet: {}, - FreeTFTMainnet: {}, - } - - testnetAssets = map[Asset]struct{}{ - TFTTestnet: {}, - FreeTFTTestnet: {}, - } -) - -// Code of the asset -func (a Asset) Code() string { - return strings.Split(string(a), ":")[0] -} - -// Issuer of the asset -func (a Asset) Issuer() string { - return strings.Split(string(a), ":")[1] -} - -// String implements Stringer interface -func (a Asset) String() string { - return string(a) -} - -// Validate if the asset is entered in the expected format -func (a Asset) validate() error { - parts := strings.Split(string(a), ":") - if len(parts) != 2 { - return fmt.Errorf("invalid amount of parts in asset string, got %d, expected 2", len(parts)) - } - if len(parts[0]) == 0 { - return errors.New("missing code in asset") - } - if len(parts[1]) == 0 { - return errors.New("missing issuer in asset") - } - return nil -} diff --git a/tools/explorer/pkg/stellar/asset_test.go b/tools/explorer/pkg/stellar/asset_test.go deleted file mode 100644 index 765fe7b58..000000000 --- a/tools/explorer/pkg/stellar/asset_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package stellar - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAssetValidation(t *testing.T) { - assets := []Asset{ - "", // empty asset -> invalid amount of parts - "TFT:24:1243", // too many parts - ":1fjdspsjafo", // missing code - "TFT:", // missing issuer - "TFT:SomethingSomethingSomething", // valid - } - - assert.Error(t, assets[0].validate(), "invalid amount of parts in asset string, got 1, expected 2") - assert.Error(t, assets[1].validate(), "invalid amount of parts in asset string, got 3, expected 2") - assert.Error(t, assets[2].validate(), "missing code in asset") - assert.Error(t, assets[3].validate(), "missing issuer in asset") - assert.NoError(t, assets[4].validate()) - assert.Equal(t, assets[4].Code(), "TFT") - assert.Equal(t, assets[4].Issuer(), "SomethingSomethingSomething") -} - -func TestTFTMainnetAsset(t *testing.T) { - assert.Equal(t, TFTMainnet.Code(), "TFT") - assert.Equal(t, TFTMainnet.Issuer(), "GBOVQKJYHXRR3DX6NOX2RRYFRCUMSADGDESTDNBDS6CDVLGVESRTAC47") -} - -func TestTFTTestnetAsset(t *testing.T) { - assert.Equal(t, TFTTestnet.Code(), "TFT") - assert.Equal(t, TFTTestnet.Issuer(), "GA47YZA3PKFUZMPLQ3B5F2E3CJIB57TGGU7SPCQT2WAEYKN766PWIMB3") -} - -func TestFreeTFTMainnetAsset(t *testing.T) { - assert.Equal(t, FreeTFTMainnet.Code(), "FreeTFT") - assert.Equal(t, FreeTFTMainnet.Issuer(), "GCBGS5TFE2BPPUVY55ZPEMWWGR6CLQ7T6P46SOFGHXEBJ34MSP6HVEUT") -} - -func TestFreeTFTTestnetAsset(t *testing.T) { - assert.Equal(t, FreeTFTTestnet.Code(), "FreeTFT") - assert.Equal(t, FreeTFTTestnet.Issuer(), "GBLDUINEFYTF7XEE7YNWA3JQS4K2VD37YU7I2YAE7R5AHZDKQXSS2J6R") -} - -func TestMainnetAssetsCodeUniqueness(t *testing.T) { - knownCodes := make(map[string]struct{}) - - for asset := range mainnetAssets { - if _, exists := knownCodes[asset.Code()]; exists { - t.Fatal("Code ", asset.Code(), " registered twice on mainnet") - } - } -} - -func TestTestnetAssetsCodeUniqueness(t *testing.T) { - knownCodes := make(map[string]struct{}) - - for asset := range testnetAssets { - if _, exists := knownCodes[asset.Code()]; exists { - t.Fatal("Code ", asset.Code(), " registered twice on testnet") - } - } -} diff --git a/tools/explorer/pkg/stellar/crypto.go b/tools/explorer/pkg/stellar/crypto.go deleted file mode 100644 index 2bc451e10..000000000 --- a/tools/explorer/pkg/stellar/crypto.go +++ /dev/null @@ -1,86 +0,0 @@ -package stellar - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/hex" - "io" - - "github.com/pkg/errors" - "golang.org/x/crypto/blake2b" -) - -type ( - key [32]byte -) - -// encryptionKey of the wallet, which is based on the keypair used to create -// this wallet -func (w *Wallet) encryptionKey() key { - // Annoyingly, we can't get the bytes of the private key, only a string form - // of the seed. So we might as well hash it again to generate the key. - return blake2b.Sum256([]byte(w.keypair.Seed())) -} - -// encrypt a seed with a given key. The encrypted seed is returned, with the -// generated nonce prepended, encoded in hex form. -// -// The used encryption is AES in GCM mode, with a 32 byte key -func encrypt(seed string, key key) (string, error) { - block, err := aes.NewCipher(key[:]) - if err != nil { - // Because we require key to be a byte array of length 32, which is a - // known valid key, and then slice it, this should never happen - return "", errors.Wrap(err, "could not setup aes encryption") - } - - aesgcm, err := cipher.NewGCM(block) - if err != nil { - return "", errors.Wrap(err, "could not set up aes gcm mode") - } - - // Nonce MUST be unique - nonce := make([]byte, aesgcm.NonceSize()) // Default nonce size is alway 12 - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return "", errors.Wrap(err, "could not initialize nonce") - } - - // append the ciphertext to the nonce - ciphertext := aesgcm.Seal(nonce, nonce, []byte(seed), nil) - - return hex.EncodeToString(ciphertext), err -} - -// decrypt a seed which was previously encrypted. The input is expected to be -// the output of the `encrypt` function: a hex encoded string, which starts with -// the nonce, followed by the actual ciphertext. -// -// This function is effectively the inverse of the `encrypt` function -func decrypt(cipherHex string, key key) (string, error) { - ciphertext, err := hex.DecodeString(cipherHex) - if err != nil { - return "", errors.Wrap(err, "failed to decode hex ciphertext") - } - - block, err := aes.NewCipher(key[:]) - if err != nil { - // Because we require key to be a byte array of length 32, which is a - // known valid key, and then slice it, this should never happen - return "", errors.Wrap(err, "could not setup aes decryption") - } - - aesgcm, err := cipher.NewGCM(block) - if err != nil { - return "", errors.Wrap(err, "could not set up aes gcm mode") - } - - // nonce is prepended to data - nonceSize := aesgcm.NonceSize() - plainText, err := aesgcm.Open(nil, ciphertext[:nonceSize], ciphertext[nonceSize:], nil) - if err != nil { - return "", errors.Wrap(err, "failed to decrypt seed") - } - - return string(plainText), nil -} diff --git a/tools/explorer/pkg/stellar/crypto_test.go b/tools/explorer/pkg/stellar/crypto_test.go deleted file mode 100644 index b8972851e..000000000 --- a/tools/explorer/pkg/stellar/crypto_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package stellar - -import ( - "crypto/rand" - "encoding/hex" - "io" - "testing" - - "github.com/stretchr/testify/assert" -) - -const ( - usedPlaintext = "very random seed by default" -) - -func TestEncrypt(t *testing.T) { - var key key - - _, err := io.ReadFull(rand.Reader, key[:]) - assert.NoError(t, err, "initializing random key") - - ciphertext, err := encrypt(usedPlaintext, key) - assert.NoError(t, err, "encrypting random seed") - - // 12 byte nonce, len(seed) byte seed, 16 byte tag, hex encoded => double size - expectedCiphertextLen := (12 + len(usedPlaintext) + 16) * 2 - assert.Equal(t, expectedCiphertextLen, len(ciphertext), "expected and actual ciphertext len") -} - -func TestDecrypt(t *testing.T) { - keyHex := "69340634a02dc317f4777246718a589f0879969eb9c0c730858e5c8e5e4fcbff" - ciphertext := "0de81f80d88426a431f2bb92699e470f14154d9283156f73a6024c80991e2bca57bbfab08c0b24052e5d1fb242c04253d6ddd73793b9f6" - - keyBytes, err := hex.DecodeString(keyHex) - assert.NoError(t, err, "could not decode key") - - assert.Equal(t, 32, len(keyBytes)) - - var key key - copy(key[:], keyBytes) - - plaintext, err := decrypt(ciphertext, key) - assert.NoError(t, err, "could not decrypt ciphertext") - - assert.Equal(t, usedPlaintext, plaintext, "expected and actual plaintext don't match") -} - -func TestEncryptionRoundTrip(t *testing.T) { - plaintext := "Very secret stuff goes here" - - var key key - _, err := io.ReadFull(rand.Reader, key[:]) - assert.NoError(t, err, "could not generate key") - - ciphertext, err := encrypt(plaintext, key) - assert.NoError(t, err, "could not encrypt plainText") - - reversedPlaintext, err := decrypt(ciphertext, key) - assert.NoError(t, err, "could not decrypt cipherText") - - assert.Equal(t, plaintext, reversedPlaintext, "original and decrypted plaintext mismatch") - -} diff --git a/tools/explorer/pkg/stellar/stellar.go b/tools/explorer/pkg/stellar/stellar.go deleted file mode 100644 index c0e0df1af..000000000 --- a/tools/explorer/pkg/stellar/stellar.go +++ /dev/null @@ -1,625 +0,0 @@ -package stellar - -import ( - "fmt" - "math/big" - "strconv" - - "github.com/rs/zerolog/log" - "github.com/stellar/go/amount" - "github.com/stellar/go/clients/horizonclient" - "github.com/stellar/go/keypair" - "github.com/stellar/go/network" - hProtocol "github.com/stellar/go/protocols/horizon" - horizoneffects "github.com/stellar/go/protocols/horizon/effects" - "github.com/stellar/go/support/errors" - "github.com/stellar/go/txnbuild" - "github.com/stellar/go/xdr" - "github.com/threefoldtech/zos/pkg/schema" -) - -type ( - // Signers is a flag type for setting the signers on the escrow accounts - Signers []string - - // PayoutInfo holds information about which address needs to receive how many funds - // for payment commands which take multiple receivers - PayoutInfo struct { - Address string - Amount xdr.Int64 - } - - // Wallet is the foundation wallet - // Payments will be funded and fees will be taken with this wallet - Wallet struct { - keypair *keypair.Full - network string - assets map[Asset]struct{} - signers Signers - } -) - -const ( - stellarPrecision = 1e7 - stellarPrecisionDigits = 7 - stellarPageLimit = 200 - stellarOneCoin = 10000000 - - - // NetworkProduction uses stellar production network - NetworkProduction = "production" - // NetworkTest uses stellar test network - NetworkTest = "testnet" - // NetworkDebug doesn't do validation, and always address validation is skipped - // Only supported by the AddressValidator - NetworkDebug = "debug" -) - -var ( - // ErrInsuficientBalance is an error that is used when there is insufficient balance - ErrInsuficientBalance = errors.New("insuficient balance") - // ErrAssetCodeNotSupported indicated the given asset code is not supported by this wallet - ErrAssetCodeNotSupported = errors.New("asset code not supported") -) - -// New stellar wallet from an optional seed. If no seed is given (i.e. empty string), -// the wallet will panic on all actions which need to be signed, or otherwise require -// a key to be loaded. -func New(seed, network string, signers []string) (*Wallet, error) { - assets := mainnetAssets - - if network == NetworkTest { - assets = testnetAssets - } - - if len(signers) < 3 && seed != "" { - log.Warn().Msg("to enable escrow account recovery, provide atleast 3 signers") - } - - w := &Wallet{ - network: network, - assets: assets, - signers: signers, - } - - var err error - if seed != "" { - w.keypair, err = keypair.ParseFull(seed) - if err != nil { - return nil, err - } - } - - return w, nil -} - -// AssetFromCode loads the full asset from a code, provided the wallet supports -// the asset code -func (w *Wallet) AssetFromCode(code string) (Asset, error) { - for asset := range w.assets { - if asset.Code() == code { - return asset, nil - } - } - return "", ErrAssetCodeNotSupported -} - -// PrecisionDigits of the underlying currencies on chain -func (w *Wallet) PrecisionDigits() int { - return stellarPrecisionDigits -} - -// PublicAddress of this wallet -func (w *Wallet) PublicAddress() string { - if w.keypair == nil { - return "" - } - return w.keypair.Address() -} - -// CreateAccount and activate it, so that it is ready to be used -// The encrypted seed of the wallet is returned, together with the public address -func (w *Wallet) CreateAccount() (string, string, error) { - client, err := w.GetHorizonClient() - if err != nil { - return "", "", err - } - newKp, err := keypair.Random() - if err != nil { - return "", "", err - } - - sourceAccount, err := w.GetAccountDetails(w.keypair.Address()) - if err != nil { - return "", "", errors.Wrap(err, "failed to get source account") - } - - err = w.activateEscrowAccount(newKp, sourceAccount, client) - if err != nil { - return "", "", errors.Wrapf(err, "failed to activate escrow account %s", newKp.Address()) - } - - // Now fetch the escrow source account to perform operations on it - sourceAccount, err = w.GetAccountDetails(newKp.Address()) - if err != nil { - return "", "", errors.Wrap(err, "failed to get escrow source account") - } - - err = w.setupEscrow(newKp, sourceAccount, client) - if err != nil { - return "", "", errors.Wrapf(err, "failed to setup escrow account %s", newKp.Address()) - } - - // encrypt the seed before it is returned - encryptedSeed, err := encrypt(newKp.Seed(), w.encryptionKey()) - if err != nil { - return "", "", errors.Wrap(err, "could not encrypt new wallet seed") - } - - return encryptedSeed, newKp.Address(), nil - -} - -func (w *Wallet) activateEscrowAccount(newKp *keypair.Full, sourceAccount hProtocol.Account, client *horizonclient.Client) error { - currency := big.NewRat(int64(w.getMinumumBalance()), stellarPrecision) - minimumBalance := currency.FloatString(stellarPrecisionDigits) - createAccountOp := txnbuild.CreateAccount{ - Destination: newKp.Address(), - Amount: minimumBalance, - } - tx := txnbuild.Transaction{ - SourceAccount: &sourceAccount, - Operations: []txnbuild.Operation{&createAccountOp}, - Timebounds: txnbuild.NewTimeout(300), - Network: w.GetNetworkPassPhrase(), - } - - txeBase64, err := tx.BuildSignEncode(w.keypair) - if err != nil { - return errors.Wrap(err, "failed to get build transaction") - } - - // Submit the transaction - _, err = client.SubmitTransactionXDR(txeBase64) - if err != nil { - hError := err.(*horizonclient.Error) - return errors.Wrap(hError, "error submitting transaction") - } - return nil -} - -// setupEscrow will setup a trustline to the correct asset and issuer -// and also setup multisig on the escrow -func (w *Wallet) setupEscrow(newKp *keypair.Full, sourceAccount hProtocol.Account, client *horizonclient.Client) error { - var operations []txnbuild.Operation - - trustlineOperation := w.setupTrustline(sourceAccount) - operations = append(operations, trustlineOperation...) - - addSignerOperations := w.setupEscrowMultisig() - if addSignerOperations != nil { - operations = append(operations, addSignerOperations...) - } - - tx := txnbuild.Transaction{ - SourceAccount: &sourceAccount, - Operations: operations, - Timebounds: txnbuild.NewTimeout(300), - Network: w.GetNetworkPassPhrase(), - } - - txeBase64, err := tx.BuildSignEncode(newKp) - if err != nil { - return errors.Wrap(err, "failed to get build transaction") - } - - // Submit the transaction - _, err = client.SubmitTransactionXDR(txeBase64) - if err != nil { - hError := err.(*horizonclient.Error) - for _, extra := range hError.Problem.Extras { - log.Debug().Msgf("%+v", extra) - } - return errors.Wrap(hError.Problem, "error submitting transaction") - } - return nil -} - -func (w *Wallet) setupTrustline(sourceAccount hProtocol.Account) []txnbuild.Operation { - ops := make([]txnbuild.Operation, 0, len(w.assets)) - for asset := range w.assets { - ops = append(ops, &txnbuild.ChangeTrust{ - SourceAccount: &sourceAccount, - Line: txnbuild.CreditAsset{ - Code: asset.Code(), - Issuer: asset.Issuer(), - }, - }) - } - return ops -} - -func (w *Wallet) setupEscrowMultisig() []txnbuild.Operation { - if len(w.signers) < 3 { - // not enough signers, don't add multisig - return nil - } - // set the threshold for the master key equal to the amount of signers - threshold := txnbuild.Threshold(len(w.signers)) - - // set the threshold to complete transaction for signers. atleast 3 signatures are required - txThreshold := txnbuild.Threshold(3) - if len(w.signers) > 3 { - txThreshold = txnbuild.Threshold(len(w.signers)/2 + 1) - } - - var operations []txnbuild.Operation - // add the signing options - addSignersOp := txnbuild.SetOptions{ - LowThreshold: txnbuild.NewThreshold(0), - MediumThreshold: txnbuild.NewThreshold(txThreshold), - HighThreshold: txnbuild.NewThreshold(txThreshold), - MasterWeight: txnbuild.NewThreshold(threshold), - } - operations = append(operations, &addSignersOp) - - // add the signers - for _, signer := range w.signers { - addSignerOperation := txnbuild.SetOptions{ - Signer: &txnbuild.Signer{ - Address: signer, - Weight: 1, - }, - } - operations = append(operations, &addSignerOperation) - } - - return operations -} - -// GetBalance gets balance for an address and a given reservation id. It also returns -// a list of addresses which funded the given address. -func (w *Wallet) GetBalance(address string, id schema.ID, asset Asset) (xdr.Int64, []string, error) { - if address == "" { - err := fmt.Errorf("trying to get the balance of an empty address. this should never happen") - log.Warn().Err(err).Send() - return 0, nil, err - } - - var total xdr.Int64 - horizonClient, err := w.GetHorizonClient() - if err != nil { - return 0, nil, err - } - - cursor := "" - - txReq := horizonclient.TransactionRequest{ - ForAccount: address, - Cursor: cursor, - Limit: stellarPageLimit, - } - - log.Info().Str("address", address).Msg("fetching balance for address") - txes, err := horizonClient.Transactions(txReq) - if err != nil { - return 0, nil, errors.Wrap(err, "could not get transactions") - } - - donors := make(map[string]struct{}) - for len(txes.Embedded.Records) != 0 { - for _, tx := range txes.Embedded.Records { - if tx.Memo == strconv.FormatInt(int64(id), 10) { - effectsReq := horizonclient.EffectRequest{ - ForTransaction: tx.Hash, - } - effects, err := horizonClient.Effects(effectsReq) - if err != nil { - log.Error().Err(err).Msgf("failed to get transaction effects") - continue - } - // first check if we have been paid - var isFunding bool - for _, effect := range effects.Embedded.Records { - if effect.GetAccount() != address { - continue - } - if effect.GetType() == "account_credited" { - creditedEffect := effect.(horizoneffects.AccountCredited) - if creditedEffect.Asset.Code != asset.Code() || - creditedEffect.Asset.Issuer != asset.Issuer() { - continue - } - parsedAmount, err := amount.Parse(creditedEffect.Amount) - if err != nil { - continue - } - isFunding = true - total += parsedAmount - } else if effect.GetType() == "account_debited" { - debitedEffect := effect.(horizoneffects.AccountDebited) - if debitedEffect.Asset.Code != asset.Code() || - debitedEffect.Asset.Issuer != asset.Issuer() { - continue - } - parsedAmount, err := amount.Parse(debitedEffect.Amount) - if err != nil { - continue - } - isFunding = false - total -= parsedAmount - } - } - if isFunding { - // we don't need to verify the asset here anymore, since this - // flag is only toggled on after that check passed in the loop - // above - for _, effect := range effects.Embedded.Records { - if effect.GetType() == "account_debited" && effect.GetAccount() != address { - donors[effect.GetAccount()] = struct{}{} - } - } - } - } - cursor = tx.PagingToken() - } - - // if the amount of records fetched is smaller than the page limit - // we can assume we are on the last page and we break to prevent another - // call to horizon - if len(txes.Embedded.Records) < stellarPageLimit { - break - } - - txReq.Cursor = cursor - log.Info().Str("address", address).Msgf("fetching balance for address with cursor: %s", cursor) - txes, err = horizonClient.Transactions(txReq) - if err != nil { - return 0, nil, errors.Wrap(err, "could not get transactions") - } - } - - donorList := []string{} - for donor := range donors { - donorList = append(donorList, donor) - } - log.Info(). - Int64("balance", int64(total)). - Str("address", address). - Int64("id", int64(id)).Msgf("status of balance for reservation") - return total, donorList, nil -} - -// Refund an escrow address for a reservation. This will transfer all funds -// for this reservation that are currently on the address (if any), to (some of) -// the addresses which these funds came from. -func (w *Wallet) Refund(encryptedSeed string, id schema.ID, asset Asset) error { - keypair, err := w.keypairFromEncryptedSeed(encryptedSeed) - if err != nil { - return errors.Wrap(err, "could not get keypair from encrypted seed") - } - - amount, funders, err := w.GetBalance(keypair.Address(), id, asset) - if err != nil { - return errors.Wrap(err, "failed to get balance") - } - // if no balance for this reservation, do nothing - if amount == 0 { - return nil - } - - sourceAccount, err := w.GetAccountDetails(keypair.Address()) - if err != nil { - return errors.Wrap(err, "failed to get source account") - } - - destination := funders[0] - - paymentOP := txnbuild.Payment{ - Destination: destination, - Amount: big.NewRat(int64(amount), stellarPrecision).FloatString(stellarPrecisionDigits), - Asset: txnbuild.CreditAsset{ - Code: asset.Code(), - Issuer: asset.Issuer(), - }, - SourceAccount: &sourceAccount, - } - - formattedMemo := fmt.Sprintf("%d", id) - memo := txnbuild.MemoText(formattedMemo) - tx := txnbuild.Transaction{ - Operations: []txnbuild.Operation{&paymentOP}, - Timebounds: txnbuild.NewTimeout(300), - Network: w.GetNetworkPassPhrase(), - Memo: memo, - } - - fundedTx, err := w.fundTransaction(&tx) - if err != nil { - return errors.Wrap(err, "failed to fund transaction") - } - - log.Debug().Int64("amount", int64(amount)).Str("destination", destination).Msg("refund") - err = w.signAndSubmitTx(&keypair, fundedTx) - if err != nil { - return errors.Wrap(err, "failed to sign and submit transaction") - } - return nil -} - -// PayoutFarmers pays a group of farmers, from an escrow account. The escrow -// account must be provided as the encrypted string of the seed. -func (w *Wallet) PayoutFarmers(encryptedSeed string, destinations []PayoutInfo, id schema.ID, asset Asset) error { - keypair, err := w.keypairFromEncryptedSeed(encryptedSeed) - if err != nil { - return errors.Wrap(err, "could not get keypair from encrypted seed") - } - sourceAccount, err := w.GetAccountDetails(keypair.Address()) - if err != nil { - return errors.Wrap(err, "failed to get source account") - } - - paymentOps := make([]txnbuild.Operation, 0, len(destinations)+1) - - for _, pi := range destinations { - paymentOps = append(paymentOps, &txnbuild.Payment{ - Destination: pi.Address, - Amount: big.NewRat(int64(pi.Amount), stellarPrecision).FloatString(stellarPrecisionDigits), - Asset: txnbuild.CreditAsset{ - Code: asset.Code(), - Issuer: asset.Issuer(), - }, - SourceAccount: &sourceAccount, - }) - } - - formattedMemo := fmt.Sprintf("%d", id) - memo := txnbuild.MemoText(formattedMemo) - tx := txnbuild.Transaction{ - Operations: paymentOps, - Timebounds: txnbuild.NewTimeout(300), - Network: w.GetNetworkPassPhrase(), - Memo: memo, - } - - fundedTx, err := w.fundTransaction(&tx) - if err != nil { - return errors.Wrap(err, "failed to fund transaction") - } - - err = w.signAndSubmitTx(&keypair, fundedTx) - if err != nil { - return errors.Wrap(err, "failed to sign and submit transaction") - } - return nil -} - -// fundTransaction funds a transaction with the foundation wallet -// For every operation in the transaction, the fee will be paid by the foundation wallet -func (w *Wallet) fundTransaction(tx *txnbuild.Transaction) (*txnbuild.Transaction, error) { - sourceAccount, err := w.GetAccountDetails(w.keypair.Address()) - if err != nil { - return &txnbuild.Transaction{}, errors.Wrap(err, "failed to get source account") - } - - // set the source account of the tx to the foundation account - tx.SourceAccount = &sourceAccount - - if len(tx.Operations) == 0 { - return &txnbuild.Transaction{}, errors.New("no operations were set on the transaction") - } - - // calculate total fee based on the operations in the transaction - tx.BaseFee = tx.BaseFee * uint32(len(tx.Operations)) - err = tx.Build() - if err != nil { - return &txnbuild.Transaction{}, errors.Wrap(err, "failed to build transaction") - } - - err = tx.Sign(w.keypair) - if err != nil { - return &txnbuild.Transaction{}, errors.Wrap(err, "failed to sign transaction") - } - - return tx, nil -} - -// signAndSubmitTx sings of on a transaction with a given keypair -// and submits it to the network -func (w *Wallet) signAndSubmitTx(keypair *keypair.Full, tx *txnbuild.Transaction) error { - client, err := w.GetHorizonClient() - if err != nil { - return errors.Wrap(err, "failed to get horizon client") - } - - err = tx.Sign(keypair) - if err != nil { - return errors.Wrap(err, "failed to sign transaction with keypair") - } - - log.Info().Msg("submitting transaction to the stellar network") - // Submit the transaction - _, err = client.SubmitTransaction(*tx) - if err != nil { - hError := err.(*horizonclient.Error) - log.Debug(). - Err(fmt.Errorf("%+v", hError.Problem.Extras)). - Msg("error submitting transaction") - return errors.Wrap(hError.Problem, "error submitting transaction") - } - return nil -} - -// GetAccountDetails gets account details based an a Stellar address -func (w *Wallet) GetAccountDetails(address string) (account hProtocol.Account, err error) { - client, err := w.GetHorizonClient() - if err != nil { - return hProtocol.Account{}, err - } - ar := horizonclient.AccountRequest{AccountID: address} - log.Info().Str("address", address).Msgf("fetching account details for address: ") - account, err = client.AccountDetail(ar) - if err != nil { - return hProtocol.Account{}, errors.Wrapf(err, "failed to get account details for account: %s", address) - } - return account, nil -} - -func (w *Wallet) keypairFromEncryptedSeed(seed string) (keypair.Full, error) { - plainSeed, err := decrypt(seed, w.encryptionKey()) - if err != nil { - return keypair.Full{}, errors.Wrap(err, "could not decrypt seed") - } - - kp, err := keypair.ParseFull(plainSeed) - if err != nil { - return keypair.Full{}, errors.Wrap(err, "could not parse seed") - } - - return *kp, nil -} - -// GetHorizonClient gets the horizon client based on the wallet's network -func (w *Wallet) GetHorizonClient() (*horizonclient.Client, error) { - switch w.network { - case "testnet": - return horizonclient.DefaultTestNetClient, nil - case "production": - return horizonclient.DefaultPublicNetClient, nil - default: - return nil, errors.New("network is not supported") - } -} - -// GetNetworkPassPhrase gets the Stellar network passphrase based on the wallet's network -func (w *Wallet) GetNetworkPassPhrase() string { - switch w.network { - case "testnet": - return network.TestNetworkPassphrase - case "production": - return network.PublicNetworkPassphrase - default: - return network.TestNetworkPassphrase - } -} - -// getMinumumBalance calculates minimum balance for an escrow account -// following formula is used: minimum Balance = (2 + # of entries) * base reserve -// entries is the amount of operations are required to setup the account -func (w *Wallet) getMinumumBalance() int { - return (2 + len(w.assets) + len(w.signers)) * (stellarOneCoin / 2) -} - -func (i *Signers) String() string { - repr := "" - for _, s := range *i { - repr += fmt.Sprintf("%s ", s) - } - return repr -} - -// Set a value on the signers flag -func (i *Signers) Set(value string) error { - *i = append(*i, value) - return nil -} diff --git a/tools/explorer/pkg/stellar/validation.go b/tools/explorer/pkg/stellar/validation.go deleted file mode 100644 index cee6f4c0a..000000000 --- a/tools/explorer/pkg/stellar/validation.go +++ /dev/null @@ -1,81 +0,0 @@ -package stellar - -import ( - "fmt" - "strconv" - - "github.com/pkg/errors" - "github.com/stellar/go/clients/horizonclient" - hProtocol "github.com/stellar/go/protocols/horizon" -) - -// AddressValidator validates stellar address -type AddressValidator struct { - network string - asset Asset -} - -// NewAddressValidator creates an address validator instance -func NewAddressValidator(network, assetCode string) (*AddressValidator, error) { - w, err := New("", network, nil) - if err != nil { - return nil, errors.Wrap(err, "could not create wallet") - } - asset, err := w.AssetFromCode(assetCode) - if err != nil { - return nil, errors.Wrap(err, "could not load asset code") - } - return &AddressValidator{network: network, asset: asset}, nil -} - -// Valid validates a stellar address, and only return nil if address is valid -func (a *AddressValidator) Valid(address string) error { - if a.network == NetworkDebug { - return nil - } - - account, err := a.getAccountDetails(address) - if err != nil { - return errors.Wrap(err, "invalid account address") - } - - issuer := a.asset.Issuer() - - for _, balance := range account.Balances { - if balance.Code != a.asset.Code() || balance.Issuer != issuer { - continue - } - limit, err := strconv.ParseFloat(balance.Limit, 64) - if err != nil { - //probably an empty string. - continue - } - if limit > 0 { - //valid address - return nil - } - } - - return fmt.Errorf("addess has no trustline") -} - -func (a *AddressValidator) getAccountDetails(address string) (account hProtocol.Account, err error) { - client, err := a.getHorizonClient() - if err != nil { - return hProtocol.Account{}, err - } - ar := horizonclient.AccountRequest{AccountID: address} - account, err = client.AccountDetail(ar) - return -} - -func (a *AddressValidator) getHorizonClient() (*horizonclient.Client, error) { - switch a.network { - case NetworkTest: - return horizonclient.DefaultTestNetClient, nil - case NetworkProduction: - return horizonclient.DefaultPublicNetClient, nil - default: - return nil, errors.New("network is not supported") - } -} diff --git a/tools/explorer/pkg/workloads/reservation.go b/tools/explorer/pkg/workloads/reservation.go deleted file mode 100644 index 07a2bb113..000000000 --- a/tools/explorer/pkg/workloads/reservation.go +++ /dev/null @@ -1,792 +0,0 @@ -package workloads - -import ( - "context" - "encoding/hex" - "encoding/json" - "fmt" - "net/http" - "strconv" - "strings" - "time" - - "github.com/gorilla/mux" - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/config" - "github.com/threefoldtech/zos/tools/explorer/models" - generated "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" - "github.com/threefoldtech/zos/tools/explorer/mw" - directory "github.com/threefoldtech/zos/tools/explorer/pkg/directory/types" - "github.com/threefoldtech/zos/tools/explorer/pkg/escrow" - escrowtypes "github.com/threefoldtech/zos/tools/explorer/pkg/escrow/types" - phonebook "github.com/threefoldtech/zos/tools/explorer/pkg/phonebook/types" - "github.com/threefoldtech/zos/tools/explorer/pkg/stellar" - "github.com/threefoldtech/zos/tools/explorer/pkg/workloads/types" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -type ( - // API struct - API struct { - escrow escrow.Escrow - } - - // ReservationCreateResponse wraps reservation create response - ReservationCreateResponse struct { - ID schema.ID `json:"reservation_id"` - EscrowInformation escrowtypes.CustomerEscrowInformation `json:"escrow_information"` - } -) - -// freeTFT currency code -const freeTFT = "FreeTFT" - -func (a *API) validAddresses(ctx context.Context, db *mongo.Database, res *types.Reservation) error { - if config.Config.Network == "" { - log.Info().Msg("escrow disabled, no validation of farmer wallet address needed") - return nil - } - - workloads := res.Workloads("") - var nodes []string - - for _, wl := range workloads { - nodes = append(nodes, wl.NodeID) - } - - farms, err := directory.FarmsForNodes(ctx, db, nodes...) - if err != nil { - return err - } - - for _, farm := range farms { - for _, a := range farm.WalletAddresses { - validator, err := stellar.NewAddressValidator(config.Config.Network, a.Asset) - if err != nil { - if errors.Is(err, stellar.ErrAssetCodeNotSupported) { - continue - } - return errors.Wrap(err, "could not initialize address validator") - } - if err := validator.Valid(a.Address); err != nil { - return err - } - } - - } - - return nil -} - -func (a *API) create(r *http.Request) (interface{}, mw.Response) { - defer r.Body.Close() - var reservation types.Reservation - if err := json.NewDecoder(r.Body).Decode(&reservation); err != nil { - return nil, mw.BadRequest(err) - } - - if reservation.Expired() { - return nil, mw.BadRequest(fmt.Errorf("creating for a reservation that expires in the past")) - } - - // we make sure those arrays are initialized correctly - // this will make updating the document in place much easier - // in later stages - reservation.SignaturesProvision = make([]generated.SigningSignature, 0) - reservation.SignaturesDelete = make([]generated.SigningSignature, 0) - reservation.SignaturesFarmer = make([]generated.SigningSignature, 0) - reservation.Results = make([]generated.Result, 0) - - reservation, err := a.pipeline(reservation, nil) - if err != nil { - // if failed to create pipeline, then - // this reservation has failed initial validation - return nil, mw.BadRequest(err) - } - - if reservation.IsAny(types.Invalid, types.Delete) { - return nil, mw.BadRequest(fmt.Errorf("invalid request wrong status '%s'", reservation.NextAction.String())) - } - - db := mw.Database(r) - - if err := a.validAddresses(r.Context(), db, &reservation); err != nil { - return nil, mw.Error(err, http.StatusFailedDependency) //FIXME: what is this strange status ? - } - - usedNodes := reservation.NodeIDs() - var freeNodes, paidNodes int - for _, nodeID := range usedNodes { - node, err := directory.NodeFilter{}.WithNodeID(nodeID).Get(r.Context(), db, false) - if err != nil { - return nil, mw.Error(err, http.StatusInternalServerError) - } - if node.FreeToUse { - freeNodes++ - } else { - paidNodes++ - } - } - - // don't allow mixed nodes - if freeNodes > 0 && paidNodes > 0 { - return nil, mw.Error(errors.New("reservation can only contain either free nodes or paid nodes, not both"), http.StatusBadRequest) - } - - currencies := []string{} - // filter out FreeTFT if its a paid reservation - if paidNodes > 0 { - for _, c := range reservation.DataReservation.Currencies { - if c != freeTFT { - currencies = append(currencies, c) - } - } - } - - // filter out anything but FreeTFT for a free reservation - if freeNodes > 0 { - for _, c := range reservation.DataReservation.Currencies { - if c == freeTFT { - currencies = append(currencies, c) - } - } - } - - var filter phonebook.UserFilter - filter = filter.WithID(schema.ID(reservation.CustomerTid)) - user, err := filter.Get(r.Context(), db) - if err != nil { - return nil, mw.BadRequest(errors.Wrapf(err, "cannot find user with id '%d'", reservation.CustomerTid)) - } - - signature, err := hex.DecodeString(reservation.CustomerSignature) - if err != nil { - return nil, mw.BadRequest(errors.Wrap(err, "invalid signature format, expecting hex encoded string")) - } - - if err := reservation.Verify(user.Pubkey, signature); err != nil { - return nil, mw.BadRequest(errors.Wrap(err, "failed to verify customer signature")) - } - - reservation.Epoch = schema.Date{Time: time.Now()} - - id, err := types.ReservationCreate(r.Context(), db, reservation) - if err != nil { - return nil, mw.Error(err) - } - - reservation, err = types.ReservationFilter{}.WithID(id).Get(r.Context(), db) - if err != nil { - return nil, mw.Error(err) - } - - escrowDetails, err := a.escrow.RegisterReservation(generated.Reservation(reservation), currencies) - if err != nil { - return nil, mw.Error(err) - } - - return ReservationCreateResponse{ - ID: reservation.ID, - EscrowInformation: escrowDetails, - }, mw.Created() -} - -func (a *API) parseID(id string) (schema.ID, error) { - v, err := strconv.ParseInt(id, 10, 64) - if err != nil { - return 0, errors.Wrap(err, "invalid id format") - } - - return schema.ID(v), nil -} - -func (a *API) pipeline(r types.Reservation, err error) (types.Reservation, error) { - if err != nil { - return r, err - } - pl, err := types.NewPipeline(r) - if err != nil { - return r, errors.Wrap(err, "failed to process reservation state pipeline") - } - - r, _ = pl.Next() - return r, nil -} - -func (a *API) get(r *http.Request) (interface{}, mw.Response) { - id, err := a.parseID(mux.Vars(r)["res_id"]) - if err != nil { - return nil, mw.BadRequest(fmt.Errorf("invalid reservation id")) - } - - var filter types.ReservationFilter - filter = filter.WithID(id) - - db := mw.Database(r) - reservation, err := a.pipeline(filter.Get(r.Context(), db)) - if err != nil { - return nil, mw.NotFound(err) - } - - return reservation, nil -} - -func (a *API) list(r *http.Request) (interface{}, mw.Response) { - var filter types.ReservationFilter - filter, err := types.ApplyQueryFilter(r, filter) - if err != nil { - return nil, mw.BadRequest(err) - } - - db := mw.Database(r) - pager := models.PageFromRequest(r) - cur, err := filter.Find(r.Context(), db, pager) - if err != nil { - return nil, mw.Error(err) - } - - defer cur.Close(r.Context()) - - total, err := filter.Count(r.Context(), db) - if err != nil { - return nil, mw.Error(err) - } - - reservations := []types.Reservation{} - - for cur.Next(r.Context()) { - var reservation types.Reservation - if err := cur.Decode(&reservation); err != nil { - // skip reservations we can not load - // this is probably an old reservation - currentID := cur.Current.Lookup("_id").Int64() - log.Error().Err(err).Int64("id", currentID).Msg("failed to decode reservation") - continue - } - - reservation, err := a.pipeline(reservation, nil) - if err != nil { - log.Error().Err(err).Int64("id", int64(reservation.ID)).Msg("failed to process reservation") - continue - } - - reservations = append(reservations, reservation) - } - - pages := fmt.Sprintf("%d", models.NrPages(total, *pager.Limit)) - return reservations, mw.Ok().WithHeader("Pages", pages) -} - -func (a *API) queued(ctx context.Context, db *mongo.Database, nodeID string, limit int64) ([]types.Workload, error) { - - type intermediate struct { - WorkloadID string `bson:"workload_id" json:"workload_id"` - User string `bson:"user" json:"user"` - Type generated.WorkloadTypeEnum `bson:"type" json:"type"` - Content bson.Raw `bson:"content" json:"content"` - Created schema.Date `bson:"created" json:"created"` - Duration int64 `bson:"duration" json:"duration"` - Signature string `bson:"signature" json:"signature"` - ToDelete bool `bson:"to_delete" json:"to_delete"` - NodeID string `json:"node_id" bson:"node_id"` - } - - workloads := make([]types.Workload, 0) - - var queue types.QueueFilter - queue = queue.WithNodeID(nodeID) - - cur, err := queue.Find(ctx, db, options.Find().SetLimit(limit)) - if err != nil { - return nil, err - } - defer cur.Close(ctx) - for cur.Next(ctx) { - // why we have intermediate struct you say? I will tell you - // Content in the workload structure is definition as of type interface{} - // bson if found a nil interface, it initialize it with bson.D (list of elements) - // so data in Content will be something like [{key: k1, value: v1}, {key: k2, value: v2}] - // which is not the same structure expected in the node - // hence we use bson.M to force it to load data in a map like {k1: v1, k2: v2} - var wl intermediate - - if err := cur.Decode(&wl); err != nil { - return workloads, err - } - - obj := generated.ReservationWorkload{ - WorkloadId: wl.WorkloadID, - User: wl.User, - Type: wl.Type, - // Content: wl.Content, - Created: wl.Created, - Duration: wl.Duration, - Signature: wl.Signature, - ToDelete: wl.ToDelete, - } - switch wl.Type { - case generated.WorkloadTypeContainer: - var data generated.Container - if err := bson.Unmarshal(wl.Content, &data); err != nil { - return nil, err - } - obj.Content = data - - case generated.WorkloadTypeVolume: - var data generated.Volume - if err := bson.Unmarshal(wl.Content, &data); err != nil { - return nil, err - } - obj.Content = data - - case generated.WorkloadTypeZDB: - var data generated.ZDB - if err := bson.Unmarshal(wl.Content, &data); err != nil { - return nil, err - } - obj.Content = data - - case generated.WorkloadTypeNetwork: - var data generated.Network - if err := bson.Unmarshal(wl.Content, &data); err != nil { - return nil, err - } - obj.Content = data - - case generated.WorkloadTypeKubernetes: - var data generated.K8S - if err := bson.Unmarshal(wl.Content, &data); err != nil { - return nil, err - } - obj.Content = data - } - - workloads = append(workloads, types.Workload{ - NodeID: wl.NodeID, - ReservationWorkload: obj, - }) - } - - return workloads, nil -} - -func (a *API) workloads(r *http.Request) (interface{}, mw.Response) { - const ( - maxPageSize = 200 - ) - - var ( - nodeID = mux.Vars(r)["node_id"] - ) - - db := mw.Database(r) - workloads, err := a.queued(r.Context(), db, nodeID, maxPageSize) - if err != nil { - return nil, mw.Error(err) - } - - if len(workloads) > maxPageSize { - return workloads, nil - } - - from, err := a.parseID(r.FormValue("from")) - if err != nil { - return nil, mw.BadRequest(err) - } - - filter := types.ReservationFilter{}.WithIDGE(from) - filter = filter.WithNodeID(nodeID) - - cur, err := filter.Find(r.Context(), db) - if err != nil { - return nil, mw.Error(err) - } - - defer cur.Close(r.Context()) - - for cur.Next(r.Context()) { - var reservation types.Reservation - if err := cur.Decode(&reservation); err != nil { - return nil, mw.Error(err) - } - - reservation, err = a.pipeline(reservation, nil) - if err != nil { - log.Error().Err(err).Int64("id", int64(reservation.ID)).Msg("failed to process reservation") - continue - } - - if reservation.NextAction == types.Delete { - if err := a.setReservationDeleted(r.Context(), db, reservation.ID); err != nil { - return nil, mw.Error(err) - } - } - - // only reservations that is in right status - if !reservation.IsAny(types.Deploy, types.Delete) { - continue - } - - workloads = append(workloads, reservation.Workloads(nodeID)...) - - if len(workloads) >= maxPageSize { - break - } - } - - return workloads, nil -} - -func (a *API) workloadGet(r *http.Request) (interface{}, mw.Response) { - gwid := mux.Vars(r)["gwid"] - - rid, err := a.parseID(strings.Split(gwid, "-")[0]) - if err != nil { - return nil, mw.BadRequest(errors.Wrap(err, "invalid reservation id part")) - } - - var filter types.ReservationFilter - filter = filter.WithID(rid) - - db := mw.Database(r) - reservation, err := a.pipeline(filter.Get(r.Context(), db)) - if err != nil { - return nil, mw.NotFound(err) - } - // we use an empty node-id in listing to return all workloads in this reservation - workloads := reservation.Workloads("") - - var workload *types.Workload - for _, wl := range workloads { - if wl.WorkloadId == gwid { - workload = &wl - break - } - } - - if workload == nil { - return nil, mw.NotFound(fmt.Errorf("workload not found")) - } - - var result struct { - types.Workload - Result []types.Result `json:"result"` - } - result.Workload = *workload - for _, rs := range reservation.Results { - if rs.WorkloadId == workload.WorkloadId { - t := types.Result(rs) - result.Result = append(result.Result, t) - break - } - } - - return result, nil -} - -func (a *API) workloadPutResult(r *http.Request) (interface{}, mw.Response) { - defer r.Body.Close() - - nodeID := mux.Vars(r)["node_id"] - gwid := mux.Vars(r)["gwid"] - - rid, err := a.parseID(strings.Split(gwid, "-")[0]) - if err != nil { - return nil, mw.BadRequest(errors.Wrap(err, "invalid reservation id part")) - } - - var result types.Result - if err := json.NewDecoder(r.Body).Decode(&result); err != nil { - return nil, mw.BadRequest(err) - } - - var filter types.ReservationFilter - filter = filter.WithID(rid) - - db := mw.Database(r) - reservation, err := a.pipeline(filter.Get(r.Context(), db)) - if err != nil { - return nil, mw.NotFound(err) - } - // we use an empty node-id in listing to return all workloads in this reservation - workloads := reservation.Workloads(nodeID) - var workload *types.Workload - for _, wl := range workloads { - if wl.WorkloadId == gwid { - workload = &wl - break - } - } - - if workload == nil { - return nil, mw.NotFound(errors.New("workload not found")) - } - - result.NodeId = nodeID - result.WorkloadId = gwid - result.Epoch = schema.Date{Time: time.Now()} - - if err := result.Verify(nodeID); err != nil { - return nil, mw.UnAuthorized(errors.Wrap(err, "invalid result signature")) - } - - if err := types.ResultPush(r.Context(), db, rid, result); err != nil { - return nil, mw.Error(err) - } - - if err := types.WorkloadPop(r.Context(), db, gwid); err != nil { - return nil, mw.Error(err) - } - - if result.State == generated.ResultStateError { - if err := a.setReservationDeleted(r.Context(), db, rid); err != nil { - return nil, mw.Error(err) - } - } else if result.State == generated.ResultStateOK { - // check if entire reservation is deployed successfully - // fetch reservation from db again to have result appended in the model - reservation, err = a.pipeline(filter.Get(r.Context(), db)) - if err != nil { - return nil, mw.NotFound(err) - } - - if reservation.IsSuccessfullyDeployed() { - a.escrow.ReservationDeployed(rid) - } - } - - return nil, mw.Created() -} - -func (a *API) workloadPutDeleted(r *http.Request) (interface{}, mw.Response) { - // WARNING: #TODO - // This method does not validate the signature of the caller - // because there is no payload in a delete call. - // may be a simple body that has "reservation id" and "signature" - // can be used, we use the reservation id to avoid using the same - // request body to delete other reservations - - // HTTP Delete should not have a body though, so may be this should be - // changed to a PUT operation. - - nodeID := mux.Vars(r)["node_id"] - gwid := mux.Vars(r)["gwid"] - - rid, err := a.parseID(strings.Split(gwid, "-")[0]) - if err != nil { - return nil, mw.BadRequest(errors.Wrap(err, "invalid reservation id part")) - } - - var filter types.ReservationFilter - filter = filter.WithID(rid) - - db := mw.Database(r) - reservation, err := a.pipeline(filter.Get(r.Context(), db)) - if err != nil { - return nil, mw.NotFound(err) - } - - // we use an empty node-id in listing to return all workloads in this reservation - workloads := reservation.Workloads(nodeID) - var workload *types.Workload - for _, wl := range workloads { - if wl.WorkloadId == gwid { - workload = &wl - break - } - } - - if workload == nil { - return nil, mw.NotFound(errors.New("workload not found")) - } - - result := reservation.ResultOf(gwid) - if result == nil { - // no result for this work load - // QUESTION: should we still mark the result as deleted? - result = &types.Result{ - WorkloadId: gwid, - Epoch: schema.Date{Time: time.Now()}, - } - } - - result.State = generated.ResultStateDeleted - - if err := types.ResultPush(r.Context(), db, rid, *result); err != nil { - return nil, mw.Error(err) - } - - if err := types.WorkloadPop(r.Context(), db, gwid); err != nil { - return nil, mw.Error(err) - } - - // get it from store again (make sure we are up to date) - reservation, err = a.pipeline(filter.Get(r.Context(), db)) - if err != nil { - return nil, mw.Error(err) - } - - if !reservation.AllDeleted() { - return nil, nil - } - - if err := types.ReservationSetNextAction(r.Context(), db, reservation.ID, generated.NextActionDeleted); err != nil { - return nil, mw.Error(err) - } - - return nil, nil -} - -func (a *API) signProvision(r *http.Request) (interface{}, mw.Response) { - defer r.Body.Close() - var signature generated.SigningSignature - - if err := json.NewDecoder(r.Body).Decode(&signature); err != nil { - return nil, mw.BadRequest(err) - } - - sig, err := hex.DecodeString(signature.Signature) - if err != nil { - return nil, mw.BadRequest(errors.Wrap(err, "invalid signature expecting hex encoded string")) - } - - id, err := a.parseID(mux.Vars(r)["res_id"]) - if err != nil { - return nil, mw.BadRequest(fmt.Errorf("invalid reservation id")) - } - - var filter types.ReservationFilter - filter = filter.WithID(id) - - db := mw.Database(r) - reservation, err := a.pipeline(filter.Get(r.Context(), db)) - if err != nil { - return nil, mw.NotFound(err) - } - - if reservation.NextAction != generated.NextActionSign { - return nil, mw.UnAuthorized(fmt.Errorf("reservation not expecting signatures")) - } - - in := func(i int64, l []int64) bool { - for _, x := range l { - if x == i { - return true - } - } - return false - } - - if !in(signature.Tid, reservation.DataReservation.SigningRequestProvision.Signers) { - return nil, mw.UnAuthorized(fmt.Errorf("signature not required for '%d'", signature.Tid)) - } - - user, err := phonebook.UserFilter{}.WithID(schema.ID(signature.Tid)).Get(r.Context(), db) - if err != nil { - return nil, mw.NotFound(errors.Wrap(err, "customer id not found")) - } - - if err := reservation.SignatureVerify(user.Pubkey, sig); err != nil { - return nil, mw.UnAuthorized(errors.Wrap(err, "failed to verify signature")) - } - - signature.Epoch = schema.Date{Time: time.Now()} - if err := types.ReservationPushSignature(r.Context(), db, id, types.SignatureProvision, signature); err != nil { - return nil, mw.Error(err) - } - - reservation, err = a.pipeline(filter.Get(r.Context(), db)) - if err != nil { - return nil, mw.Error(err) - } - - if reservation.NextAction == generated.NextActionDeploy { - types.WorkloadPush(r.Context(), db, reservation.Workloads("")...) - } - - return nil, mw.Created() -} - -func (a *API) signDelete(r *http.Request) (interface{}, mw.Response) { - defer r.Body.Close() - var signature generated.SigningSignature - - if err := json.NewDecoder(r.Body).Decode(&signature); err != nil { - return nil, mw.BadRequest(err) - } - - sig, err := hex.DecodeString(signature.Signature) - if err != nil { - return nil, mw.BadRequest(errors.Wrap(err, "invalid signature expecting hex encoded string")) - } - - id, err := a.parseID(mux.Vars(r)["res_id"]) - if err != nil { - return nil, mw.BadRequest(fmt.Errorf("invalid reservation id")) - } - - var filter types.ReservationFilter - filter = filter.WithID(id) - - db := mw.Database(r) - reservation, err := a.pipeline(filter.Get(r.Context(), db)) - if err != nil { - return nil, mw.NotFound(err) - } - - in := func(i int64, l []int64) bool { - for _, x := range l { - if x == i { - return true - } - } - return false - } - - if !in(signature.Tid, reservation.DataReservation.SigningRequestDelete.Signers) { - return nil, mw.UnAuthorized(fmt.Errorf("signature not required for '%d'", signature.Tid)) - } - - user, err := phonebook.UserFilter{}.WithID(schema.ID(signature.Tid)).Get(r.Context(), db) - if err != nil { - return nil, mw.NotFound(errors.Wrap(err, "customer id not found")) - } - - if err := reservation.SignatureVerify(user.Pubkey, sig); err != nil { - return nil, mw.UnAuthorized(errors.Wrap(err, "failed to verify signature")) - } - - signature.Epoch = schema.Date{Time: time.Now()} - if err := types.ReservationPushSignature(r.Context(), db, id, types.SignatureDelete, signature); err != nil { - return nil, mw.Error(err) - } - - reservation, err = a.pipeline(filter.Get(r.Context(), db)) - if err != nil { - return nil, mw.Error(err) - } - - if reservation.NextAction != generated.NextActionDelete { - return nil, mw.Created() - } - - if err := a.setReservationDeleted(r.Context(), db, reservation.ID); err != nil { - return nil, mw.Error(err) - } - - if err := types.WorkloadPush(r.Context(), db, reservation.Workloads("")...); err != nil { - return nil, mw.Error(err) - } - - return nil, mw.Created() -} - -func (a *API) setReservationDeleted(ctx context.Context, db *mongo.Database, id schema.ID) error { - // cancel reservation escrow in case the reservation has not yet been deployed - a.escrow.ReservationCanceled(id) - return types.ReservationSetNextAction(ctx, db, id, generated.NextActionDelete) -} diff --git a/tools/explorer/pkg/workloads/setup.go b/tools/explorer/pkg/workloads/setup.go deleted file mode 100644 index 44684a053..000000000 --- a/tools/explorer/pkg/workloads/setup.go +++ /dev/null @@ -1,36 +0,0 @@ -package workloads - -import ( - "context" - "net/http" - - "github.com/gorilla/mux" - "github.com/threefoldtech/zos/tools/explorer/mw" - "github.com/threefoldtech/zos/tools/explorer/pkg/escrow" - "github.com/threefoldtech/zos/tools/explorer/pkg/workloads/types" - "go.mongodb.org/mongo-driver/mongo" -) - -// Setup injects and initializes directory package -func Setup(parent *mux.Router, db *mongo.Database, escrow escrow.Escrow) error { - if err := types.Setup(context.TODO(), db); err != nil { - return err - } - - var api API - api.escrow = escrow - reservations := parent.PathPrefix("/reservations").Subrouter() - - reservations.HandleFunc("", mw.AsHandlerFunc(api.create)).Methods(http.MethodPost).Name("reservation-create") - reservations.HandleFunc("", mw.AsHandlerFunc(api.list)).Methods(http.MethodGet).Name("reservation-list") - reservations.HandleFunc("/{res_id:\\d+}", mw.AsHandlerFunc(api.get)).Methods(http.MethodGet).Name("reservation-get") - reservations.HandleFunc("/{res_id:\\d+}/sign/provision", mw.AsHandlerFunc(api.signProvision)).Methods(http.MethodPost).Name("reservation-sign-provision") - reservations.HandleFunc("/{res_id:\\d+}/sign/delete", mw.AsHandlerFunc(api.signDelete)).Methods(http.MethodPost).Name("reservation-sign-delete") - - reservations.HandleFunc("/workloads/{node_id}", mw.AsHandlerFunc(api.workloads)).Queries("from", "{from:\\d+}").Methods(http.MethodGet).Name("workloads-poll") - reservations.HandleFunc("/workloads/{gwid:\\d+-\\d+}", mw.AsHandlerFunc(api.workloadGet)).Methods(http.MethodGet).Name("workload-get") - reservations.HandleFunc("/workloads/{gwid:\\d+-\\d+}/{node_id}", mw.AsHandlerFunc(api.workloadPutResult)).Methods(http.MethodPut).Name("workloads-results") - reservations.HandleFunc("/workloads/{gwid:\\d+-\\d+}/{node_id}", mw.AsHandlerFunc(api.workloadPutDeleted)).Methods(http.MethodDelete).Name("workloads-deleted") - - return nil -} diff --git a/tools/explorer/pkg/workloads/types/pipeline.go b/tools/explorer/pkg/workloads/types/pipeline.go deleted file mode 100644 index 36991d18a..000000000 --- a/tools/explorer/pkg/workloads/types/pipeline.go +++ /dev/null @@ -1,150 +0,0 @@ -package types - -import ( - "time" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - generated "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" -) - -// Pipeline changes Reservation R as defined by the reservation pipeline -// returns new reservation object, and true if the reservation has changed -type Pipeline struct { - r Reservation -} - -// NewPipeline creates a reservation pipeline, all reservation must be processes -// through the pipeline before any action is taken. This will always make sure -// that reservation is in the right state. -func NewPipeline(R Reservation) (*Pipeline, error) { - if err := R.validate(); err != nil { - return nil, errors.Wrap(err, "invalid reservation object") - } - - return &Pipeline{R}, nil -} - -func (p *Pipeline) checkProvisionSignatures() bool { - - // Note: signatures validatation already done in the - // signature add operation. Here we just make sure the - // required quorum has been reached - - request := p.r.DataReservation.SigningRequestProvision - log.Debug().Msgf("%+v", request) - if request.QuorumMin == 0 { - return true - } - - in := func(i int64, l []int64) bool { - for _, x := range l { - if x == i { - return true - } - } - return false - } - - signatures := p.r.SignaturesProvision - var count int64 - for _, signature := range signatures { - if !in(signature.Tid, request.Signers) { - continue - } - count++ - } - - return count >= request.QuorumMin -} - -func (p *Pipeline) checkDeleteSignatures() bool { - - // Note: signatures validatation already done in the - // signature add operation. Here we just make sure the - // required quorum has been reached - request := p.r.DataReservation.SigningRequestDelete - if request.QuorumMin == 0 { - // if min quorum is zero, then there is no way - // you can trigger deleting of this reservation - return false - } - - in := func(i int64, l []int64) bool { - for _, x := range l { - if x == i { - return true - } - } - return false - } - - signatures := p.r.SignaturesDelete - var count int64 - for _, signature := range signatures { - if !in(signature.Tid, request.Signers) { - continue - } - count++ - } - - return count >= request.QuorumMin -} - -// Next gets new modified reservation, and true if the reservation has changed from the input -func (p *Pipeline) Next() (Reservation, bool) { - if p.r.NextAction == generated.NextActionDelete || - p.r.NextAction == generated.NextActionDeleted { - return p.r, false - } - - slog := log.With().Int64("id", int64(p.r.ID)).Logger() - - // reseration expiration time must be checked, once expiration time is exceeded - // the reservation must be deleted - if p.r.Expired() || p.checkDeleteSignatures() { - // reservation has expired - // set its status (next action) to delete - slog.Debug().Msg("expired or to be deleted") - p.r.NextAction = generated.NextActionDelete - return p.r, true - } - - if p.r.DataReservation.ExpirationProvisioning.Before(time.Now()) && !p.r.IsSuccessfullyDeployed() { - log.Debug().Msg("provision expiration reached and not fully provisionned") - p.r.NextAction = generated.NextActionDelete - return p.r, true - } - - current := p.r.NextAction - modified := false - for { - switch p.r.NextAction { - case generated.NextActionCreate: - slog.Debug().Msg("ready to sign") - p.r.NextAction = generated.NextActionSign - case generated.NextActionSign: - // this stage will not change unless all - if p.checkProvisionSignatures() { - slog.Debug().Msg("ready to pay") - p.r.NextAction = generated.NextActionPay - } - case generated.NextActionPay: - // Pay needs to block, until the escrow moves us past this point - slog.Debug().Msg("awaiting reservation payment") - case generated.NextActionDeploy: - //nothing to do - slog.Debug().Msg("let's deploy") - } - - if current == p.r.NextAction { - // no more changes in stage - break - } - - current = p.r.NextAction - modified = true - } - - return p.r, modified -} diff --git a/tools/explorer/pkg/workloads/types/reservation.go b/tools/explorer/pkg/workloads/types/reservation.go deleted file mode 100644 index d563f4542..000000000 --- a/tools/explorer/pkg/workloads/types/reservation.go +++ /dev/null @@ -1,644 +0,0 @@ -package types - -import ( - "bytes" - "context" - "encoding/hex" - "encoding/json" - "fmt" - "net/http" - "reflect" - "strconv" - "time" - - "github.com/pkg/errors" - "github.com/threefoldtech/zos/pkg" - "github.com/threefoldtech/zos/pkg/crypto" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models" - generated "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const ( - // ReservationCollection db collection name - ReservationCollection = "reservation" - queueCollection = "workqueue" -) - -const ( - // Create action - Create = generated.NextActionCreate - // Sign action - Sign = generated.NextActionSign - // Pay action - Pay = generated.NextActionPay - // Deploy action - Deploy = generated.NextActionDeploy - // Delete action - Delete = generated.NextActionDelete - // Invalid action - Invalid = generated.NextActionInvalid - // Deleted action - Deleted = generated.NextActionDeleted -) - -// ApplyQueryFilter parese the query string -func ApplyQueryFilter(r *http.Request, filter ReservationFilter) (ReservationFilter, error) { - var err error - customerid, err := models.QueryInt(r, "customer_tid") - if err != nil { - return nil, errors.Wrap(err, "customer_tid should be an integer") - } - if customerid != 0 { - filter = filter.WithCustomerID(int(customerid)) - } - sNextAction := r.FormValue("next_action") - if len(sNextAction) != 0 { - nextAction, err := strconv.ParseInt(sNextAction, 10, 0) - if err != nil { - return nil, errors.Wrap(err, "next_action should be an integer") - } - filter = filter.WithNextAction(generated.NextActionEnum(nextAction)) - } - return filter, nil -} - -// ReservationFilter type -type ReservationFilter bson.D - -// WithID filter reservation with ID -func (f ReservationFilter) WithID(id schema.ID) ReservationFilter { - return append(f, bson.E{Key: "_id", Value: id}) -} - -// WithIDGE return find reservations with -func (f ReservationFilter) WithIDGE(id schema.ID) ReservationFilter { - return append(f, bson.E{ - Key: "_id", Value: bson.M{"$gte": id}, - }) -} - -// WithNextAction filter reservations with next action -func (f ReservationFilter) WithNextAction(action generated.NextActionEnum) ReservationFilter { - return append(f, bson.E{ - Key: "next_action", Value: action, - }) -} - -// WithCustomerID filter reservation on customer -func (f ReservationFilter) WithCustomerID(customerID int) ReservationFilter { - return append(f, bson.E{ - Key: "customer_tid", Value: customerID, - }) - -} - -// WithNodeID searsch reservations with NodeID -func (f ReservationFilter) WithNodeID(id string) ReservationFilter { - //data_reservation.{containers, volumes, zdbs, networks, kubernetes}.node_id - // we need to search ALL types for any reservation that has the node ID - or := []bson.M{} - for _, typ := range []string{"containers", "volumes", "zdbs", "kubernetes"} { - key := fmt.Sprintf("data_reservation.%s.node_id", typ) - or = append(or, bson.M{key: id}) - } - - // network workload is special because node id is set on the network_resources. - or = append(or, bson.M{"data_reservation.networks.network_resources.node_id": id}) - - // we find any reservation that has this node ID set. - return append(f, bson.E{Key: "$or", Value: or}) -} - -// Or returns filter that reads as (f or o) -func (f ReservationFilter) Or(o ReservationFilter) ReservationFilter { - return ReservationFilter{ - bson.E{ - Key: "$or", - Value: bson.A{f, o}, - }, - } -} - -// Get gets single reservation that matches the filter -func (f ReservationFilter) Get(ctx context.Context, db *mongo.Database) (reservation Reservation, err error) { - if f == nil { - f = ReservationFilter{} - } - - result := db.Collection(ReservationCollection).FindOne(ctx, f) - if err = result.Err(); err != nil { - return - } - - err = result.Decode(&reservation) - return -} - -// Find all users that matches filter -func (f ReservationFilter) Find(ctx context.Context, db *mongo.Database, opts ...*options.FindOptions) (*mongo.Cursor, error) { - if f == nil { - f = ReservationFilter{} - } - return db.Collection(ReservationCollection).Find(ctx, f, opts...) -} - -// Count number of documents matching -func (f ReservationFilter) Count(ctx context.Context, db *mongo.Database) (int64, error) { - col := db.Collection(ReservationCollection) - if f == nil { - f = ReservationFilter{} - } - - return col.CountDocuments(ctx, f) -} - -// Reservation is a wrapper around generated type -type Reservation generated.Reservation - -// Validate that the reservation is valid -func (r *Reservation) validate() error { - if r.CustomerTid == 0 { - return fmt.Errorf("customer_tid is required") - } - - if len(r.CustomerSignature) == 0 { - return fmt.Errorf("customer_signature is required") - } - - var data generated.ReservationData - - if err := json.Unmarshal([]byte(r.Json), &data); err != nil { - return errors.Wrap(err, "invalid json data on reservation") - } - - if !reflect.DeepEqual(r.DataReservation, data) { - return fmt.Errorf("json data does not match the reservation data") - } - - ids := make(map[int64]struct{}) - - // yes, it's ugly. live with it. - for _, w := range r.DataReservation.Containers { - if _, ok := ids[w.WorkloadId]; ok { - return fmt.Errorf("conflicting workload ID '%d'", w.WorkloadId) - } - ids[w.WorkloadId] = struct{}{} - } - - for _, w := range r.DataReservation.Networks { - if _, ok := ids[w.WorkloadId]; ok { - return fmt.Errorf("conflicting workload ID '%d'", w.WorkloadId) - } - ids[w.WorkloadId] = struct{}{} - } - - for _, w := range r.DataReservation.Zdbs { - if _, ok := ids[w.WorkloadId]; ok { - return fmt.Errorf("conflicting workload ID '%d'", w.WorkloadId) - } - ids[w.WorkloadId] = struct{}{} - } - - for _, w := range r.DataReservation.Volumes { - if _, ok := ids[w.WorkloadId]; ok { - return fmt.Errorf("conflicting workload ID '%d'", w.WorkloadId) - } - ids[w.WorkloadId] = struct{}{} - } - - for _, w := range r.DataReservation.Kubernetes { - if _, ok := ids[w.WorkloadId]; ok { - return fmt.Errorf("conflicting workload ID '%d'", w.WorkloadId) - } - ids[w.WorkloadId] = struct{}{} - } - - return nil -} - -// Verify signature against Reserveration.JSON -// pk is the public key used as verification key in hex encoded format -// the signature is the signature to verify (in raw binary format) -func (r *Reservation) Verify(pk string, sig []byte) error { - key, err := crypto.KeyFromHex(pk) - if err != nil { - return errors.Wrap(err, "invalid verification key") - } - - return crypto.Verify(key, []byte(r.Json), sig) -} - -// SignatureVerify is similar to Verify but the verification is done -// against `str(Reservation.ID) + Reservation.JSON` -func (r *Reservation) SignatureVerify(pk string, sig []byte) error { - key, err := crypto.KeyFromHex(pk) - if err != nil { - return errors.Wrap(err, "invalid verification key") - } - - var buf bytes.Buffer - if _, err := buf.WriteString(fmt.Sprint(int64(r.ID))); err != nil { - return errors.Wrap(err, "failed to write id to buffer") - } - - if _, err := buf.WriteString(r.Json); err != nil { - return errors.Wrap(err, "failed to write json to buffer") - } - - return crypto.Verify(key, buf.Bytes(), sig) -} - -// Expired checks if this reservation has expired -func (r *Reservation) Expired() bool { - return time.Until(r.DataReservation.ExpirationReservation.Time) <= 0 -} - -// IsAny checks if the reservation status is any of the given status -func (r *Reservation) IsAny(status ...generated.NextActionEnum) bool { - for _, s := range status { - if r.NextAction == s { - return true - } - } - - return false -} - -//ResultOf return result of a workload ID -func (r *Reservation) ResultOf(id string) *Result { - for _, result := range r.Results { - if result.WorkloadId == id { - r := Result(result) - return &r - } - } - - return nil -} - -// AllDeleted checks of all workloads has been marked -func (r *Reservation) AllDeleted() bool { - // check if all workloads have been deleted. - for _, wl := range r.Workloads("") { - result := r.ResultOf(wl.WorkloadId) - if result == nil || - result.State != generated.ResultStateDeleted { - return false - } - } - - return true -} - -// Workloads returns all reservation workloads (filter by nodeID) -// if nodeID is empty, return all workloads -func (r *Reservation) Workloads(nodeID string) []Workload { - data := &r.DataReservation - var workloads []Workload - for _, wl := range data.Containers { - if len(nodeID) > 0 && wl.NodeId != nodeID { - continue - } - workload := Workload{ - ReservationWorkload: generated.ReservationWorkload{ - WorkloadId: fmt.Sprintf("%d-%d", r.ID, wl.WorkloadId), - User: fmt.Sprint(r.CustomerTid), - Type: generated.WorkloadTypeContainer, - Content: wl, - Created: r.Epoch, - Duration: int64(data.ExpirationReservation.Sub(r.Epoch.Time).Seconds()), - ToDelete: r.NextAction == Delete || r.NextAction == Deleted, - }, - NodeID: wl.NodeId, - } - - workloads = append(workloads, workload) - } - - for _, wl := range data.Volumes { - if len(nodeID) > 0 && wl.NodeId != nodeID { - continue - } - workload := Workload{ - ReservationWorkload: generated.ReservationWorkload{ - WorkloadId: fmt.Sprintf("%d-%d", r.ID, wl.WorkloadId), - User: fmt.Sprint(r.CustomerTid), - Type: generated.WorkloadTypeVolume, - Content: wl, - Created: r.Epoch, - Duration: int64(data.ExpirationReservation.Sub(r.Epoch.Time).Seconds()), - ToDelete: r.NextAction == Delete || r.NextAction == Deleted, - }, - NodeID: wl.NodeId, - } - - workloads = append(workloads, workload) - } - - for _, wl := range data.Zdbs { - if len(nodeID) > 0 && wl.NodeId != nodeID { - continue - } - workload := Workload{ - ReservationWorkload: generated.ReservationWorkload{ - WorkloadId: fmt.Sprintf("%d-%d", r.ID, wl.WorkloadId), - User: fmt.Sprint(r.CustomerTid), - Type: generated.WorkloadTypeZDB, - Content: wl, - Created: r.Epoch, - Duration: int64(data.ExpirationReservation.Sub(r.Epoch.Time).Seconds()), - ToDelete: r.NextAction == Delete || r.NextAction == Deleted, - }, - NodeID: wl.NodeId, - } - - workloads = append(workloads, workload) - } - - for _, wl := range data.Kubernetes { - if len(nodeID) > 0 && wl.NodeId != nodeID { - continue - } - workload := Workload{ - ReservationWorkload: generated.ReservationWorkload{ - WorkloadId: fmt.Sprintf("%d-%d", r.ID, wl.WorkloadId), - User: fmt.Sprint(r.CustomerTid), - Type: generated.WorkloadTypeKubernetes, - Content: wl, - Created: r.Epoch, - Duration: int64(data.ExpirationReservation.Sub(r.Epoch.Time).Seconds()), - ToDelete: r.NextAction == Delete || r.NextAction == Deleted, - }, - NodeID: wl.NodeId, - } - - workloads = append(workloads, workload) - } - - for _, wl := range data.Networks { - for _, nr := range wl.NetworkResources { - - if len(nodeID) > 0 && nr.NodeId != nodeID { - continue - } - // QUESTION: the problem here is that we have multiple workloads that - // has the same global workload-id, hence it's gonna be a problem - // when the node report their results. because it means only last - // result is what is gonna be visible. We need to (may be) change - // the workload id to have the network resource index - workload := Workload{ - ReservationWorkload: generated.ReservationWorkload{ - WorkloadId: fmt.Sprintf("%d-%d", r.ID, wl.WorkloadId), - User: fmt.Sprint(r.CustomerTid), - Type: generated.WorkloadTypeNetwork, - Content: wl, - Created: r.Epoch, - Duration: int64(data.ExpirationReservation.Sub(r.Epoch.Time).Seconds()), - ToDelete: r.NextAction == Delete || r.NextAction == Deleted, - }, - NodeID: nr.NodeId, - } - - workloads = append(workloads, workload) - } - } - - return workloads -} - -// IsSuccessfullyDeployed check if all the workloads defined in the reservation -// have sent a positive result -func (r *Reservation) IsSuccessfullyDeployed() bool { - succeeded := false - if len(r.Results) >= len(r.Workloads("")) { - succeeded = true - for _, result := range r.Results { - if result.State != generated.ResultStateOK { - succeeded = false - break - } - } - } - return succeeded -} - -// NodeIDs used by this reservation -func (r *Reservation) NodeIDs() []string { - ids := make(map[string]struct{}) - for _, w := range r.DataReservation.Containers { - ids[w.NodeId] = struct{}{} - } - - for _, w := range r.DataReservation.Networks { - for _, nr := range w.NetworkResources { - ids[nr.NodeId] = struct{}{} - } - } - - for _, w := range r.DataReservation.Zdbs { - ids[w.NodeId] = struct{}{} - } - - for _, w := range r.DataReservation.Volumes { - ids[w.NodeId] = struct{}{} - } - - for _, w := range r.DataReservation.Kubernetes { - ids[w.NodeId] = struct{}{} - } - - nodeIDs := make([]string, 0, len(ids)) - - for nid := range ids { - nodeIDs = append(nodeIDs, nid) - } - - return nodeIDs -} - -// ReservationCreate save new reservation to database. -// NOTE: use reservations only that are returned from calling Pipeline.Next() -// no validation is done here, this is just a CRUD operation -func ReservationCreate(ctx context.Context, db *mongo.Database, r Reservation) (schema.ID, error) { - id := models.MustID(ctx, db, ReservationCollection) - r.ID = id - - _, err := db.Collection(ReservationCollection).InsertOne(ctx, r) - if err != nil { - return 0, err - } - - return id, nil -} - -// ReservationSetNextAction update the reservation next action in db -func ReservationSetNextAction(ctx context.Context, db *mongo.Database, id schema.ID, action generated.NextActionEnum) error { - var filter ReservationFilter - filter = filter.WithID(id) - - col := db.Collection(ReservationCollection) - _, err := col.UpdateOne(ctx, filter, bson.M{ - "$set": bson.M{ - "next_action": action, - }, - }) - - if err != nil { - return err - } - - return nil -} - -// SignatureMode type -type SignatureMode string - -const ( - // SignatureProvision mode - SignatureProvision SignatureMode = "signatures_provision" - // SignatureDelete mode - SignatureDelete SignatureMode = "signatures_delete" -) - -//ReservationPushSignature push signature to reservation -func ReservationPushSignature(ctx context.Context, db *mongo.Database, id schema.ID, mode SignatureMode, signature generated.SigningSignature) error { - - var filter ReservationFilter - filter = filter.WithID(id) - col := db.Collection(ReservationCollection) - // NOTE: this should be a transaction not a bulk write - // but i had so many issues with transaction, and i couldn't - // get it to work. so I used bulk write in place instead - // until we figure this issue out. - // Note, the reason we don't just use addToSet is the signature - // object always have the current 'time' which means it's a different - // value than the one in the document even if it has same user id. - _, err := col.BulkWrite(ctx, []mongo.WriteModel{ - mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate( - bson.M{ - "$pull": bson.M{ - string(mode): bson.M{"tid": signature.Tid}, - }, - }), - mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate( - bson.M{ - "$addToSet": bson.M{ - string(mode): signature, - }, - }), - }, options.BulkWrite().SetOrdered(true)) - - return err -} - -// Workload is a wrapper around generated TfgridWorkloadsReservationWorkload1 type -type Workload struct { - generated.ReservationWorkload `bson:",inline"` - NodeID string `json:"node_id" bson:"node_id"` -} - -// QueueFilter for workloads in temporary queue -type QueueFilter bson.D - -// WithNodeID search queue with node-id -func (f QueueFilter) WithNodeID(nodeID string) QueueFilter { - return append(f, bson.E{Key: "node_id", Value: nodeID}) -} - -// Find runs the filter, and return a cursor -func (f QueueFilter) Find(ctx context.Context, db *mongo.Database, opts ...*options.FindOptions) (*mongo.Cursor, error) { - col := db.Collection(queueCollection) - return col.Find(ctx, f, opts...) -} - -// WorkloadPush pushes a workload to the queue -func WorkloadPush(ctx context.Context, db *mongo.Database, w ...Workload) error { - col := db.Collection(queueCollection) - docs := make([]interface{}, 0, len(w)) - for _, wl := range w { - docs = append(docs, wl) - } - _, err := col.InsertMany(ctx, docs) - - return err -} - -// WorkloadPop removes workload from queue -func WorkloadPop(ctx context.Context, db *mongo.Database, id string) error { - col := db.Collection(queueCollection) - _, err := col.DeleteOne(ctx, bson.M{"workload_id": id}) - - return err -} - -// Result is a wrapper around TfgridWorkloadsReservationResult1 type -type Result generated.Result - -func (r *Result) encode() ([]byte, error) { - buf := &bytes.Buffer{} - if err := buf.WriteByte(byte(r.State)); err != nil { - return nil, err - } - if _, err := buf.WriteString(r.Message); err != nil { - return nil, err - } - if _, err := buf.Write(r.DataJson); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -// Verify that the signature matches the result data -func (r *Result) Verify(pk string) error { - sig, err := hex.DecodeString(r.Signature) - if err != nil { - return errors.Wrap(err, "invalid signature expecting hex encoded") - } - - key, err := crypto.KeyFromID(pkg.StrIdentifier(pk)) - if err != nil { - return errors.Wrap(err, "invalid verification key") - } - - bytes, err := r.encode() - if err != nil { - return err - } - - return crypto.Verify(key, bytes, sig) -} - -// ResultPush pushes result to a reservation result array. -// NOTE: this is just a crud operation, no validation is done here -func ResultPush(ctx context.Context, db *mongo.Database, id schema.ID, result Result) error { - col := db.Collection(ReservationCollection) - var filter ReservationFilter - filter = filter.WithID(id) - - // we don't care if we couldn't delete old result. - // in case it never existed, or the array is nil. - col.UpdateOne(ctx, filter, bson.M{ - "$pull": bson.M{ - "results": bson.M{ - "workload_id": result.WorkloadId, - "node_id": result.NodeId, - }, - }, - }) - - _, err := col.UpdateOne(ctx, filter, bson.D{ - { - Key: "$push", - Value: bson.M{ - "results": result, - }, - }, - }) - - return err -} diff --git a/tools/explorer/pkg/workloads/types/reservation_test.go b/tools/explorer/pkg/workloads/types/reservation_test.go deleted file mode 100644 index ecb8e40fd..000000000 --- a/tools/explorer/pkg/workloads/types/reservation_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package types - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestValidation(t *testing.T) { - const ( - input = `{ - "id": 4, - "json": "{\"expiration_reservation\": 1582937602, \"expiration_provisioning\": 1582937602}", - "data_reservation": { - "description": "", - "signing_request_provision": { - "signers": null, - "quorum_min": 0 - }, - "signing_request_delete": { - "signers": null, - "quorum_min": 0 - }, - "containers": null, - "volumes": null, - "zdbs": null, - "networks": null, - "kubernetes": null, - "expiration_provisioning": 1582937602, - "expiration_reservation": 1582937602 - }, - "customer_tid": 7, - "customer_signature": "225af4e3bc6cdaa44e877821bcbb6a8201b9c080ef66dc192a0511927dc51b70447b14d663f492feec5434ee1a1b720785d3d9de6fd5e594c8010720ddb7eb00", - "next_action": 3, - "signatures_provision": null, - "signatures_farmer": null, - "signatures_delete": null, - "epoch": 0, - "results": null - }` - ) - var reservation Reservation - err := json.Unmarshal([]byte(input), &reservation) - require.NoError(t, err) - - err = reservation.validate() - require.NoError(t, err) -} diff --git a/tools/explorer/pkg/workloads/types/setup.go b/tools/explorer/pkg/workloads/types/setup.go deleted file mode 100644 index 09cfa980b..000000000 --- a/tools/explorer/pkg/workloads/types/setup.go +++ /dev/null @@ -1,52 +0,0 @@ -package types - -import ( - "context" - "fmt" - - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" -) - -// Setup sets up indexes for types, must be called at least -// Onetime during the life time of the object -func Setup(ctx context.Context, db *mongo.Database) error { - col := db.Collection(ReservationCollection) - indexes := []mongo.IndexModel{ - { - Keys: bson.M{"data_reservation.networks.network_resources.node_id": 1}, - }, - } - - for _, typ := range []string{"containers", "volumes", "zdbs", "kubernetes"} { - indexes = append( - indexes, - mongo.IndexModel{ - Keys: bson.M{fmt.Sprintf("data_reservation.%s.node_id", typ): 1}, - }, - ) - - } - indexes = append(indexes, mongo.IndexModel{Keys: bson.M{"next_action": 1}}) - indexes = append(indexes, mongo.IndexModel{Keys: bson.M{"customer_tid": 1}}) - - if _, err := col.Indexes().CreateMany(ctx, indexes); err != nil { - return err - } - - col = db.Collection(queueCollection) - indexes = []mongo.IndexModel{ - { - Keys: bson.M{"node_id": 1}, - }, - { - Keys: bson.M{"workload_id": 1}, - }, - } - - if _, err := col.Indexes().CreateMany(ctx, indexes); err != nil { - return err - } - - return nil -} diff --git a/tools/explorer/readme.md b/tools/explorer/readme.md deleted file mode 100644 index ff73e6c65..000000000 --- a/tools/explorer/readme.md +++ /dev/null @@ -1,77 +0,0 @@ -# Explorer - -The explorer is the component that is responsible to host all the public information about the nodes running 0-OS, the farms, the users identity and the capacity reservations. - -The explorer exposes both a web UI and a REST API. - -- The web UI allows users to discover all the nodes and farms in the grid. -- The API is used by nodes and users. - -## Prerequisites - -Following commands can be passed to the explorer: - -| Command | Description -| --- | --- -| `-listen` | listen address, default :8080 -| `-dbConf` | connection string to mongo database, default mongodb://localhost:27017 -| `-name` | database name, default explorer -| `-seed` | Seed of a valid Stellar address that has balance to support running the explorer -| `-network` | Stellar network, default testnet. Values can be (production, testnet) -| `-flush-escrows` | Remove the currently known escrow accounts and associated addresses in the db, then exit -| `-backupsigners` | Repeatable flag, expects a valid Stellar address. If 3 are provided, multisig on the escrow accounts will be enabled. This is needed if one wishes to recover funds on the escrow accounts. -| `-foundation-address` | Sets the "foundation address", this address will receive the payout of a reservation that is destined for the foundation, if any. If not set, the public address of the seed will be used. - -> If a seed is passed to the explorer, payments for reservation will be enabled. - -> To recover funds for an escrow account, check following docs: [tools/stellar/readme.md](tools/stellar/readme.md) - -## reservation payment - -When a reservation is created on the explorer, the client also needs to specify -a `currencies` field. This field contains the currency codes of all the currencies -the client is willing to use to pay for the reservation. The explorer will filter -out currencies which are not accepted based on the reservation being destined for -paid nodes or free to use nodes. In case of the former, only `FreeTFT` is currently -accepted. In case of the latter, anything but `FreeTFT` is accepted. For all currencies -which are acceptable for the reservation, the escrow then checks if all farms support -this currency. When a match is found, this currency, is set as currency to pay -the reservation with. At all times **only** 1 currency will be used for a single reservation. -The chosen currency is eventually communicated back to the client at the end of the -reservation create flow, in the `asset` field, in the form `:`. If there is no match for any -currency, then there will be no escrow setup, and the reservation will not be completed. - -## currency management - -The explorer escrow is able to handle multiple different currencies at once. Which exact -currencies it accepts, can be found in [pkg/stellar/asset.go](pkg/stellar/asset.go). -In order to support a new currency in the wallet, it suffices to add the asset here, -and load it in either the mainnet or testnet asset map. The eventual payouts in -the event of a successful reservation are based on a payout distribution, which -is linked to a specific asset, and can be found in [pkg/escrow/payout_distribution.go](pkg/escrow/payout_distribution.go). - -## managing encrypted seeds for escrow accounts - -The seeds of the escrow accounts are encrypted with a key based on the seed used -to start the explorer. This means that changing this seed will cause decryption -of these seeds, and thus their usage by the explorer, to fail. If for any reason -the seed used to start the explorer changes, the operator will need to clear existing -escrow accounts and their associated seeds. To this end, the explorer can be restarted -with the `-flush-escrows` flag. When this flag is passed, confirmation will be asked -on the command line if a user really wants to remove this data from the db. If the -operator changes his/her mind, the explorer will exit, and needs to be restarted -without this flag. - -### disposing of encrypted seeds - -It is possible, that the addresses used by an escrow are currently active, i.e. -a user has created a reservation and is in the process of paying for it. Although -it is technically possible to swap the addresses in the escrow, the user will still -try to pay to the old address, so this case can't really be handled. In order to not -lose funds however, it is encouraged to back up the accounts before they are removed. -If the explorer is started with the multisig feature enabled by providing sufficient -backup signers, the funds (if any) on the escrow address can still be recovered, and -returned to their rightfull owners, by creating multisigs with the backup signers -for the addresses. Note that the public addresses are not encrypted, as such, even -if the seed used to start the explorer is lost completely, the escrow funds can still -be recovered diff --git a/tools/schemac/main.go b/tools/schemac/main.go index cfb9517b8..24a8ed2f2 100644 --- a/tools/schemac/main.go +++ b/tools/schemac/main.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - "github.com/threefoldtech/zos/pkg/schema" + "github.com/threefoldtech/tfexplorer/schema" ) func handle(in io.Reader, pkg, dir string) error { diff --git a/tools/stellar/multisig.go b/tools/stellar/multisig.go index b912ac785..9ddb47267 100644 --- a/tools/stellar/multisig.go +++ b/tools/stellar/multisig.go @@ -8,7 +8,7 @@ import ( "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" "github.com/stellar/go/txnbuild" - "github.com/threefoldtech/zos/tools/explorer/pkg/stellar" + "github.com/threefoldtech/tfexplorer/pkg/stellar" "github.com/urfave/cli" ) diff --git a/tools/tffarmer/farm.go b/tools/tffarmer/farm.go index d67fceb00..16dcc7330 100644 --- a/tools/tffarmer/farm.go +++ b/tools/tffarmer/farm.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" + "github.com/threefoldtech/tfexplorer/models/generated/directory" + "github.com/threefoldtech/tfexplorer/schema" "github.com/urfave/cli" ) diff --git a/tools/tffarmer/main.go b/tools/tffarmer/main.go index 56a202654..2f66d22f0 100644 --- a/tools/tffarmer/main.go +++ b/tools/tffarmer/main.go @@ -7,8 +7,8 @@ import ( "os" + "github.com/threefoldtech/tfexplorer/client" "github.com/threefoldtech/zos/pkg/identity" - "github.com/threefoldtech/zos/tools/client" "github.com/urfave/cli" ) diff --git a/tools/tffarmer/network.go b/tools/tffarmer/network.go index a7a7f1a91..067ddb345 100644 --- a/tools/tffarmer/network.go +++ b/tools/tffarmer/network.go @@ -4,8 +4,8 @@ import ( "fmt" "net" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/explorer/models/generated/directory" + "github.com/threefoldtech/tfexplorer/models/generated/directory" + "github.com/threefoldtech/tfexplorer/schema" "github.com/urfave/cli" ) diff --git a/tools/tfuser/cmds_identity.go b/tools/tfuser/cmds_identity.go index de4e17253..7629b4783 100644 --- a/tools/tfuser/cmds_identity.go +++ b/tools/tfuser/cmds_identity.go @@ -7,9 +7,9 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/threefoldtech/tfexplorer/client" + "github.com/threefoldtech/tfexplorer/models/generated/phonebook" "github.com/threefoldtech/zos/pkg/identity" - "github.com/threefoldtech/zos/tools/client" - "github.com/threefoldtech/zos/tools/explorer/models/generated/phonebook" "github.com/urfave/cli" ) diff --git a/tools/tfuser/cmds_live.go b/tools/tfuser/cmds_live.go index 74c399db0..8c07e9b3d 100644 --- a/tools/tfuser/cmds_live.go +++ b/tools/tfuser/cmds_live.go @@ -8,10 +8,10 @@ import ( "time" "github.com/rs/zerolog/log" - "github.com/threefoldtech/zos/tools/explorer/models/generated/workloads" + "github.com/threefoldtech/tfexplorer/models/generated/workloads" "gopkg.in/yaml.v2" - "github.com/threefoldtech/zos/pkg/schema" + "github.com/threefoldtech/tfexplorer/schema" "github.com/urfave/cli" ) diff --git a/tools/tfuser/cmds_provision.go b/tools/tfuser/cmds_provision.go index 159444a9c..43b70865f 100644 --- a/tools/tfuser/cmds_provision.go +++ b/tools/tfuser/cmds_provision.go @@ -12,11 +12,11 @@ import ( "github.com/pkg/errors" "github.com/stellar/go/xdr" + "github.com/threefoldtech/tfexplorer/client" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/crypto" "github.com/threefoldtech/zos/pkg/provision" - "github.com/threefoldtech/zos/pkg/schema" - "github.com/threefoldtech/zos/tools/client" + "github.com/threefoldtech/tfexplorer/schema" "github.com/urfave/cli" ) diff --git a/tools/tfuser/main.go b/tools/tfuser/main.go index 10854300d..02e4acf5b 100644 --- a/tools/tfuser/main.go +++ b/tools/tfuser/main.go @@ -3,8 +3,8 @@ package main import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "github.com/threefoldtech/tfexplorer/client" "github.com/threefoldtech/zos/pkg/identity" - "github.com/threefoldtech/zos/tools/client" "fmt" "os" @@ -400,6 +400,33 @@ func main() { }, }, }, + { + Name: "gateway", + Usage: "Provision TCP proxy and DNS configuration", + Subcommands: []cli.Command{ + { + Name: "proxy", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "node", + Usage: "node id for the generated workload", + Required: true, + }, + cli.StringFlag{ + Name: "domain, s", + Usage: "Domain to proxy", + Required: true, + }, + cli.StringFlag{ + Name: "destination, d", + Usage: "Destination ip:port", + Required: true, + }, + }, + // Action: generateProxy, + }, + }, + }, { Name: "kubernetes", Usage: "Provision a vm running a kubernetes server or agent on a node", From fed7470f2ac1ea0db1fc03a991a32b1ca0f14c0e Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Sun, 19 Apr 2020 10:13:01 +0200 Subject: [PATCH 03/14] make provision engine fully configurable by user --- cmds/provisiond/main.go | 23 ++--- pkg/identity/keys.go | 2 - pkg/provision/engine.go | 171 ++++++++++++----------------------- pkg/provision/feedback.go | 98 ++++++++++++++++++++ pkg/provision/provision.go | 8 +- pkg/provision/reservation.go | 102 +-------------------- pkg/provision/source.go | 22 +++-- pkg/zdb/zdb.go | 3 +- 8 files changed, 190 insertions(+), 239 deletions(-) create mode 100644 pkg/provision/feedback.go diff --git a/cmds/provisiond/main.go b/cmds/provisiond/main.go index a6b0972ba..024c0183f 100644 --- a/cmds/provisiond/main.go +++ b/cmds/provisiond/main.go @@ -107,19 +107,16 @@ func main() { ctx = provision.WithZBus(ctx, client) ctx = provision.WithOwnerCache(ctx, ownerCache) - // From here we start the real provision engine that will live - // for the rest of the life of the node - source := provision.CombinedSource( - provision.PollSource(provision.ReservationPollerFromWorkloads(cl.Workloads), nodeID), - provision.NewDecommissionSource(localStore), - ) - - engine := provision.New( - provision.WithNodeID(nodeID.Identity()), - provision.WithSource(source), - provision.WithCache(localStore), - provision.WithExplorer(cl), - ) + engine := provision.New(provision.EngineOps{ + NodeID: nodeID.Identity(), + Cache: localStore, + Source: provision.CombinedSource( + provision.PollSource(provision.ReservationPollerFromWorkloads(cl.Workloads, provision.WorkloadToProvisionType), nodeID), + provision.NewDecommissionSource(localStore), + ), + Feedback: provision.NewExplorerFeedback(cl, provision.ToSchemaType), + Signer: identity, + }) server.Register(zbus.ObjectID{Name: module, Version: "0.0.1"}, pkg.ProvisionMonitor(engine)) diff --git a/pkg/identity/keys.go b/pkg/identity/keys.go index 0bb69cddf..49a5ffd31 100644 --- a/pkg/identity/keys.go +++ b/pkg/identity/keys.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/jbenet/go-base58" - "github.com/rs/zerolog/log" "github.com/threefoldtech/zos/pkg/versioned" "golang.org/x/crypto/ed25519" @@ -76,7 +75,6 @@ func LoadSeed(path string) ([]byte, error) { // LoadKeyPair reads a seed from a file located at path and re-create a // KeyPair using the seed func LoadKeyPair(path string) (k KeyPair, err error) { - log.Warn().Msg("LoadKeyPair is deprecated, please use UserIdentity struct") return loadKeyPair(path) } diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go index 6f48674a0..511c8123d 100644 --- a/pkg/provision/engine.go +++ b/pkg/provision/engine.go @@ -7,10 +7,7 @@ import ( "fmt" "time" - "github.com/threefoldtech/tfexplorer/client" - "github.com/threefoldtech/tfexplorer/models/generated/directory" "github.com/threefoldtech/zos/pkg" - "github.com/threefoldtech/zos/pkg/stubs" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -34,57 +31,29 @@ type Feedbacker interface { UpdateReservedResources(nodeID string, c Counters) error } -type Engine struct { - nodeID string - source ReservationSource - store ReservationCache - cl *client.Client - provisioners map[ReservationType]Provisioner - decomissioners map[ReservationType]Decommissioner -} - -type option func(*Engine) - -type engineOptions struct { - nodeID string - source ReservationSource - store ReservationCache - cl *client.Client -} - -func WithNodeID(nodeID string) option { - return func(e *Engine) { - e.nodeID = nodeID - } -} -func WithSource(s ReservationSource) option { - return func(e *Engine) { - e.source = s - } -} - -func WithCache(c ReservationCache) option { - return func(e *Engine) { - e.store = c - } +type Signer interface { + Sign(b []byte) ([]byte, error) } -func WithExplorer(c *client.Client) option { - return func(e *Engine) { - e.cl = c - } +type Engine struct { + nodeID string + source ReservationSource + cache ReservationCache + feedback Feedbacker + // cl *client.Client + provisioners map[ReservationType]ProvisionerFunc + decomissioners map[ReservationType]DecommissionerFunc + signer Signer } -func WithProvisioners(m map[ReservationType]Provisioner) option { - return func(e *Engine) { - e.provisioners = m - } -} - -func WithDecomissioners(m map[ReservationType]Decommissioner) option { - return func(e *Engine) { - e.decomissioners = m - } +type EngineOps struct { + NodeID string + Source ReservationSource + Cache ReservationCache + Feedback Feedbacker + Provisioners map[ReservationType]ProvisionerFunc + Decomissioners map[ReservationType]DecommissionerFunc + Signer Signer } // New creates a new engine. Once started, the engine @@ -93,17 +62,22 @@ func WithDecomissioners(m map[ReservationType]Decommissioner) option { // the default implementation is a single threaded worker. so it process // one reservation at a time. On error, the engine will log the error. and // continue to next reservation. -func New(opts ...option) *Engine { - e := &Engine{} - for _, f := range opts { - f(e) +func New(opts EngineOps) *Engine { + if opts.Provisioners == nil { + opts.Provisioners = provisioners } - if e.provisioners == nil { - e.provisioners = provisioners + + if opts.Decomissioners == nil { + opts.Decomissioners = decommissioners } - if e.decomissioners == nil { - e.decomissioners = decommissioners + e := &Engine{ + nodeID: opts.NodeID, + source: opts.Source, + cache: opts.Cache, + feedback: opts.Feedback, + decomissioners: opts.Decomissioners, + signer: opts.Signer, } return e @@ -154,39 +128,12 @@ func (e *Engine) Run(ctx context.Context) error { } } - if err := e.updateReservedCapacity(); err != nil { - log.Error().Err(err).Msg("failed to updated the used resources") + if err := e.feedback.UpdateReservedResources(e.nodeID, e.cache.Counters()); err != nil { + log.Error().Err(err).Msg("failed to updated the capacity counters") } - } - } -} -func (e Engine) capacityUsed() (directory.ResourceAmount, directory.WorkloadAmount) { - counters := e.store.Counters() - - resources := directory.ResourceAmount{ - Cru: counters.CRU.Current(), - Mru: float64(counters.MRU.Current()) / float64(gib), - Sru: float64(counters.SRU.Current()) / float64(gib), - Hru: float64(counters.HRU.Current()) / float64(gib), - } - - workloads := directory.WorkloadAmount{ - Volume: uint16(counters.volumes.Current()), - Container: uint16(counters.containers.Current()), - ZDBNamespace: uint16(counters.zdbs.Current()), - K8sVM: uint16(counters.vms.Current()), - Network: uint16(counters.networks.Current()), + } } - return resources, workloads -} - -func (e *Engine) updateReservedCapacity() error { - resources, workloads := e.capacityUsed() - log.Info().Msgf("reserved resource %+v", resources) - log.Info().Msgf("provisionned workloads %+v", workloads) - - return e.cl.Directory.NodeUpdateUsedResources(e.nodeID, resources, workloads) } func (e *Engine) provision(ctx context.Context, r *Reservation) error { @@ -194,18 +141,28 @@ func (e *Engine) provision(ctx context.Context, r *Reservation) error { return errors.Wrapf(err, "failed validation of reservation") } - fn, ok := provisioners[r.Type] + fn, ok := e.provisioners[r.Type] if !ok { return fmt.Errorf("type of reservation not supported: %s", r.Type) } - _, err := e.store.Get(r.ID) + _, err := e.cache.Get(r.ID) if err == nil { log.Info().Str("id", r.ID).Msg("reservation already deployed") return nil } result, err := fn(ctx, r) + if err != nil { + log.Error(). + Err(err). + Str("id", r.ID). + Msgf("failed to apply provision") + } else { + log.Info(). + Str("result", fmt.Sprintf("%v", result)). + Msgf("workload deployed") + } if replyErr := e.reply(ctx, r, err, result); replyErr != nil { log.Error().Err(replyErr).Msg("failed to send result to BCDB") @@ -215,7 +172,7 @@ func (e *Engine) provision(ctx context.Context, r *Reservation) error { return err } - if err := e.store.Add(r); err != nil { + if err := e.cache.Add(r); err != nil { return errors.Wrapf(err, "failed to cache reservation %s locally", r.ID) } @@ -223,19 +180,19 @@ func (e *Engine) provision(ctx context.Context, r *Reservation) error { } func (e *Engine) decommission(ctx context.Context, r *Reservation) error { - fn, ok := decommissioners[r.Type] + fn, ok := e.decomissioners[r.Type] if !ok { return fmt.Errorf("type of reservation not supported: %s", r.Type) } - exists, err := e.store.Exists(r.ID) + exists, err := e.cache.Exists(r.ID) if err != nil { return errors.Wrapf(err, "failed to check if reservation %s exists in cache", r.ID) } if !exists { log.Info().Str("id", r.ID).Msg("reservation not provisioned, no need to decomission") - if err := e.cl.Workloads.WorkloadPutDeleted(e.nodeID, r.ID); err != nil { + if err := e.feedback.Deleted(e.nodeID, r.ID); err != nil { log.Error().Err(err).Str("id", r.ID).Msg("failed to mark reservation as deleted") } return nil @@ -246,39 +203,29 @@ func (e *Engine) decommission(ctx context.Context, r *Reservation) error { return errors.Wrap(err, "decommissioning of reservation failed") } - if err := e.store.Remove(r.ID); err != nil { + if err := e.cache.Remove(r.ID); err != nil { return errors.Wrapf(err, "failed to remove reservation %s from cache", r.ID) } - if err := e.cl.Workloads.WorkloadPutDeleted(e.nodeID, r.ID); err != nil { + if err := e.feedback.Deleted(e.nodeID, r.ID); err != nil { return errors.Wrap(err, "failed to mark reservation as deleted") } return nil } -func (e *Engine) reply(ctx context.Context, r *Reservation, rErr error, info interface{}) error { +func (e *Engine) reply(ctx context.Context, r *Reservation, err error, info interface{}) error { log.Debug().Str("id", r.ID).Msg("sending reply for reservation") - zbus := GetZBus(ctx) - identity := stubs.NewIdentityManagerStub(zbus) result := &Result{ Type: r.Type, Created: time.Now(), ID: r.ID, } - - if rErr != nil { - log.Error(). - Err(rErr). - Str("id", r.ID). - Msgf("failed to apply provision") - result.Error = rErr.Error() + if err != nil { + result.Error = err.Error() result.State = StateError } else { - log.Info(). - Str("result", fmt.Sprintf("%v", info)). - Msgf("workload deployed") result.State = StateOk } @@ -293,13 +240,13 @@ func (e *Engine) reply(ctx context.Context, r *Reservation, rErr error, info int return errors.Wrap(err, "failed to convert the result to byte for signature") } - sig, err := identity.Sign(b) + sig, err := e.signer.Sign(b) if err != nil { return errors.Wrap(err, "failed to signed the result") } result.Signature = hex.EncodeToString(sig) - return e.cl.Workloads.WorkloadPutResult(e.nodeID, r.ID, result.ToSchemaType()) + return e.feedback.Feedback(e.nodeID, result) } func (e *Engine) Counters(ctx context.Context) <-chan pkg.ProvisionCounters { @@ -311,7 +258,7 @@ func (e *Engine) Counters(ctx context.Context) <-chan pkg.ProvisionCounters { case <-ctx.Done(): } - c := e.store.Counters() + c := e.cache.Counters() pc := pkg.ProvisionCounters{ Container: int64(c.containers.Current()), Network: int64(c.networks.Current()), diff --git a/pkg/provision/feedback.go b/pkg/provision/feedback.go new file mode 100644 index 000000000..d008af926 --- /dev/null +++ b/pkg/provision/feedback.go @@ -0,0 +1,98 @@ +package provision + +import ( + "fmt" + + "github.com/rs/zerolog/log" + "github.com/threefoldtech/tfexplorer/client" + "github.com/threefoldtech/tfexplorer/models/generated/directory" + "github.com/threefoldtech/tfexplorer/models/generated/workloads" + "github.com/threefoldtech/tfexplorer/schema" +) + +type ExplorerFeedback struct { + client *client.Client + converter ResultConverterFunc +} + +func NewExplorerFeedback(client *client.Client, converter ResultConverterFunc) *ExplorerFeedback { + if converter == nil { + converter = ToSchemaType + } + return &ExplorerFeedback{ + client: client, + converter: converter, + } +} + +func (e *ExplorerFeedback) Feedback(nodeID string, r *Result) error { + wr, err := e.converter(*r) + if err != nil { + return fmt.Errorf("failed to convert result into schema type: %w") + } + + return e.client.Workloads.WorkloadPutResult(nodeID, r.ID, *wr) +} + +func (e *ExplorerFeedback) Deleted(nodeID, id string) error { + return e.client.Workloads.WorkloadPutDeleted(nodeID, id) +} + +func (e *ExplorerFeedback) UpdateReservedResources(nodeID string, c Counters) error { + resources := directory.ResourceAmount{ + Cru: c.CRU.Current(), + Mru: float64(c.MRU.Current()) / float64(gib), + Sru: float64(c.SRU.Current()) / float64(gib), + Hru: float64(c.HRU.Current()) / float64(gib), + } + + workloads := directory.WorkloadAmount{ + Volume: uint16(c.volumes.Current()), + Container: uint16(c.containers.Current()), + ZDBNamespace: uint16(c.zdbs.Current()), + K8sVM: uint16(c.vms.Current()), + Network: uint16(c.networks.Current()), + } + log.Info().Msgf("reserved resource %+v", resources) + log.Info().Msgf("provisionned workloads %+v", workloads) + return e.client.Directory.NodeUpdateUsedResources(nodeID, resources, workloads) +} + +// ToSchemaType converts result to schema type +func ToSchemaType(r Result) (*workloads.Result, error) { + var rType workloads.ResultCategoryEnum + switch r.Type { + case VolumeReservation: + rType = workloads.ResultCategoryVolume + case ContainerReservation: + rType = workloads.ResultCategoryContainer + case ZDBReservation: + rType = workloads.ResultCategoryZDB + case NetworkReservation: + rType = workloads.ResultCategoryNetwork + case KubernetesReservation: + rType = workloads.ResultCategoryK8S + // case ResultCategoryProxy: + // rType = workloads.ResultCategoryProxy + // case ResultCategoryReverseProxy: + // rType = workloads.ResultCategoryReverseProxy, + // case ResultCategorySubDomain: + // rType = workloads.ResultCategorySubDomain, + // case ResultCategoryDomainDelegate: + // rType = workloads.ResultCategoryDomainDelegate, + default: + return nil, fmt.Errorf("unknown reservation type: %s", r.Type) + } + + result := workloads.Result{ + Category: rType, + WorkloadId: r.ID, + DataJson: r.Data, + Signature: r.Signature, + State: workloads.ResultStateEnum(r.State), + Message: r.Error, + Epoch: schema.Date{Time: r.Created}, + } + + return &result, nil +} diff --git a/pkg/provision/provision.go b/pkg/provision/provision.go index e5f0ec532..ef21dc13d 100644 --- a/pkg/provision/provision.go +++ b/pkg/provision/provision.go @@ -19,14 +19,14 @@ type ReservationSource interface { Reservations(ctx context.Context) <-chan *Reservation } -type Provisioner func(ctx context.Context, reservation *Reservation) (interface{}, error) -type Decommissioner func(ctx context.Context, reservation *Reservation) error +type ProvisionerFunc func(ctx context.Context, reservation *Reservation) (interface{}, error) +type DecommissionerFunc func(ctx context.Context, reservation *Reservation) error var ( // provisioners defines the entry point for the different // reservation provisioners. Currently only containers are // supported. - provisioners = map[ReservationType]Provisioner{ + provisioners = map[ReservationType]ProvisionerFunc{ ContainerReservation: containerProvision, VolumeReservation: volumeProvision, NetworkReservation: networkProvision, @@ -35,7 +35,7 @@ var ( KubernetesReservation: kubernetesProvision, } - decommissioners = map[ReservationType]Decommissioner{ + decommissioners = map[ReservationType]DecommissionerFunc{ ContainerReservation: containerDecommission, VolumeReservation: volumeDecommission, NetworkReservation: networkDecommission, diff --git a/pkg/provision/reservation.go b/pkg/provision/reservation.go index f63e5a7df..4c0c1aa3d 100644 --- a/pkg/provision/reservation.go +++ b/pkg/provision/reservation.go @@ -10,10 +10,10 @@ import ( "github.com/pkg/errors" "github.com/threefoldtech/tfexplorer/models/generated/workloads" + "github.com/threefoldtech/tfexplorer/schema" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/container/logger" "github.com/threefoldtech/zos/pkg/container/stats" - "github.com/threefoldtech/tfexplorer/schema" "github.com/threefoldtech/zos/pkg/versioned" ) @@ -150,75 +150,6 @@ func (r *Reservation) validate() error { return nil } -// ToSchemaType creates a TfgridReservation1 from zos provision types -func (r *Reservation) ToSchemaType() (res workloads.Reservation, err error) { - - w, err := workloadFromRaw(r.Data, r.Type) - if err != nil { - return res, err - } - - switch r.Type { - case ContainerReservation: - res.DataReservation.Containers = []workloads.Container{ - containerReservation(w, r.NodeID), - } - case VolumeReservation: - res.DataReservation.Volumes = []workloads.Volume{ - volumeReservation(w, r.NodeID), - } - case ZDBReservation: - res.DataReservation.Zdbs = []workloads.ZDB{ - zdbReservation(w, r.NodeID), - } - case NetworkReservation: - res.DataReservation.Networks = []workloads.Network{ - networkReservation(w), - } - case KubernetesReservation: - res.DataReservation.Kubernetes = []workloads.K8S{ - k8sReservation(w, r.NodeID), - } - } - - res.Epoch = schema.Date{Time: r.Created} - res.DataReservation.ExpirationReservation = schema.Date{Time: r.Created.Add(r.Duration)} - res.DataReservation.ExpirationProvisioning = schema.Date{Time: r.Created.Add(2 * time.Minute)} - - return res, nil -} - -func workloadFromRaw(s json.RawMessage, t ReservationType) (interface{}, error) { - switch t { - case ContainerReservation: - c := Container{} - err := json.Unmarshal([]byte(s), &c) - return c, err - - case VolumeReservation: - v := Volume{} - err := json.Unmarshal([]byte(s), &v) - return v, err - - case NetworkReservation: - n := pkg.Network{} - err := json.Unmarshal([]byte(s), &n) - return n, err - - case ZDBReservation: - z := ZDB{} - err := json.Unmarshal([]byte(s), &z) - return z, err - - case KubernetesReservation: - k := Kubernetes{} - err := json.Unmarshal([]byte(s), &k) - return k, err - } - - return nil, fmt.Errorf("unsupported reservation type %v", t) -} - func networkReservation(i interface{}) workloads.Network { n := i.(pkg.Network) network := workloads.Network{ @@ -446,34 +377,3 @@ func (r *Result) Bytes() ([]byte, error) { return buf.Bytes(), nil } - -// ToSchemaType converts result to schema type -func (r *Result) ToSchemaType() workloads.Result { - var rType workloads.ResultCategoryEnum - switch r.Type { - case VolumeReservation: - rType = workloads.ResultCategoryVolume - case ContainerReservation: - rType = workloads.ResultCategoryContainer - case ZDBReservation: - rType = workloads.ResultCategoryZDB - case NetworkReservation: - rType = workloads.ResultCategoryNetwork - case KubernetesReservation: - rType = workloads.ResultCategoryK8S - default: - panic(fmt.Errorf("unknown reservation type: %s", r.Type)) - } - - result := workloads.Result{ - Category: rType, - WorkloadId: r.ID, - DataJson: r.Data, - Signature: r.Signature, - State: workloads.ResultStateEnum(r.State), - Message: r.Error, - Epoch: schema.Date{Time: r.Created}, - } - - return result -} diff --git a/pkg/provision/source.go b/pkg/provision/source.go index 72a18a1da..c41dc1759 100644 --- a/pkg/provision/source.go +++ b/pkg/provision/source.go @@ -11,6 +11,7 @@ import ( "github.com/rs/zerolog/log" "github.com/threefoldtech/tfexplorer/client" + "github.com/threefoldtech/tfexplorer/models/generated/workloads" "github.com/threefoldtech/zos/pkg" ) @@ -37,8 +38,13 @@ type ReservationPoller interface { Poll(nodeID pkg.Identifier, from uint64) ([]*Reservation, error) } +type ReservationConverterFunc func(w workloads.ReservationWorkload) (*Reservation, error) +type ResultConverterFunc func(result Result) (*workloads.Result, error) + type reservationPoller struct { - wl client.Workloads + wl client.Workloads + inputConv ReservationConverterFunc + outputConv ResultConverterFunc } // provisionOrder is used to sort the workload type @@ -53,14 +59,16 @@ var provisionOrder = map[ReservationType]int{ } func (r *reservationPoller) Poll(nodeID pkg.Identifier, from uint64) ([]*Reservation, error) { + list, err := r.wl.Workloads(nodeID.Identity(), from) if err != nil { - return nil, err + return nil, fmt.Errorf("error while retrieving workloads from explorer: %w", err) } result := make([]*Reservation, 0, len(list)) for _, wl := range list { - r, err := WorkloadToProvisionType(wl) + log.Info().Msgf("convert %+v", wl) + r, err := r.inputConv(wl) if err != nil { return nil, err } @@ -77,8 +85,11 @@ func (r *reservationPoller) Poll(nodeID pkg.Identifier, from uint64) ([]*Reserva } // ReservationPollerFromWorkloads returns a reservation poller from client.Workloads -func ReservationPollerFromWorkloads(wl client.Workloads) ReservationPoller { - return &reservationPoller{wl: wl} +func ReservationPollerFromWorkloads(wl client.Workloads, inputConv ReservationConverterFunc) ReservationPoller { + return &reservationPoller{ + wl: wl, + inputConv: inputConv, + } } // PollSource does a long poll on address to get new and to be deleted @@ -94,7 +105,6 @@ func PollSource(store ReservationPoller, nodeID pkg.Identifier) ReservationSourc } func (s *pollSource) Reservations(ctx context.Context) <-chan *Reservation { - log.Info().Msg("start reservation http source") ch := make(chan *Reservation) // on the first run we will get all the reservation diff --git a/pkg/zdb/zdb.go b/pkg/zdb/zdb.go index 910841626..20b3eba6c 100644 --- a/pkg/zdb/zdb.go +++ b/pkg/zdb/zdb.go @@ -105,7 +105,8 @@ func newRedisPool(address string) (*redis.Pool, error) { return nil }, - MaxActive: 10, + MaxActive: 3, + MaxIdle: 3, IdleTimeout: 1 * time.Minute, Wait: true, }, nil From 9406e67c0049816e8650a42ceb091c9cf92dea32 Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Sun, 19 Apr 2020 10:40:47 +0200 Subject: [PATCH 04/14] provisiond: create provisioner object all the provision logic is extracted into this object. The provision engine is now fully generic and can be configured to be used with any workloads --- cmds/provisiond/main.go | 21 +++++++------- pkg/provision/container.go | 31 +++++++++----------- pkg/provision/debug.go | 12 ++++---- pkg/provision/engine.go | 12 +------- pkg/provision/kubernetes.go | 48 ++++++++++++++----------------- pkg/provision/midleware.go | 26 ----------------- pkg/provision/network.go | 12 ++++---- pkg/provision/provision.go | 57 ++++++++++++++++++++++++------------- pkg/provision/volume.go | 14 ++++----- pkg/provision/zdb.go | 53 ++++++++++++++-------------------- 10 files changed, 124 insertions(+), 162 deletions(-) diff --git a/cmds/provisiond/main.go b/cmds/provisiond/main.go index 024c0183f..e3c7aa92e 100644 --- a/cmds/provisiond/main.go +++ b/cmds/provisiond/main.go @@ -81,12 +81,12 @@ func main() { if err != nil { log.Fatal().Err(err).Msg("failed to connect to message broker") } - client, err := zbus.NewRedisClient(msgBrokerCon) + zbusCl, err := zbus.NewRedisClient(msgBrokerCon) if err != nil { log.Fatal().Err(err).Msg("fail to connect to message broker server") } - identity := stubs.NewIdentityManagerStub(client) + identity := stubs.NewIdentityManagerStub(zbusCl) nodeID := identity.NodeID() // to get reservation from tnodb @@ -99,13 +99,11 @@ func main() { if err != nil { log.Fatal().Err(err).Msg("failed to create local reservation store") } - // to get the user ID of a reservation - ownerCache := provision.NewCache(localStore, provision.ReservationGetterFromWorkloads(cl.Workloads)) - // create context and add middlewares - ctx := context.Background() - ctx = provision.WithZBus(ctx, client) - ctx = provision.WithOwnerCache(ctx, ownerCache) + provisioner := provision.NewProvisioner( + // use to get the user ID of a reservation + provision.NewCache(localStore, provision.ReservationGetterFromWorkloads(cl.Workloads)), + zbusCl) engine := provision.New(provision.EngineOps{ NodeID: nodeID.Identity(), @@ -114,8 +112,10 @@ func main() { provision.PollSource(provision.ReservationPollerFromWorkloads(cl.Workloads, provision.WorkloadToProvisionType), nodeID), provision.NewDecommissionSource(localStore), ), - Feedback: provision.NewExplorerFeedback(cl, provision.ToSchemaType), - Signer: identity, + Provisioners: provisioner.Provisioners, + Decomissioners: provisioner.Decommissioners, + Feedback: provision.NewExplorerFeedback(cl, provision.ToSchemaType), + Signer: identity, }) server.Register(zbus.ObjectID{Name: module, Version: "0.0.1"}, pkg.ProvisionMonitor(engine)) @@ -124,6 +124,7 @@ func main() { Str("broker", msgBrokerCon). Msg("starting provision module") + ctx := context.Background() ctx, _ = utils.WithSignal(ctx) utils.OnDone(ctx, func(_ error) { log.Info().Msg("shutting down") diff --git a/pkg/provision/container.go b/pkg/provision/container.go index 5ec181003..40f61656c 100644 --- a/pkg/provision/container.go +++ b/pkg/provision/container.go @@ -75,18 +75,15 @@ type ContainerCapacity struct { Memory uint64 `json:"memory"` } -func containerProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { - return containerProvisionImpl(ctx, reservation) +func (p *Provisioner) containerProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { + return p.containerProvisionImpl(ctx, reservation) } // ContainerProvision is entry point to container reservation -func containerProvisionImpl(ctx context.Context, reservation *Reservation) (ContainerResult, error) { - client := GetZBus(ctx) - cache := GetOwnerCache(ctx) - - containerClient := stubs.NewContainerModuleStub(client) - flistClient := stubs.NewFlisterStub(client) - storageClient := stubs.NewStorageModuleStub(client) +func (p *Provisioner) containerProvisionImpl(ctx context.Context, reservation *Reservation) (ContainerResult, error) { + containerClient := stubs.NewContainerModuleStub(p.zbus) + flistClient := stubs.NewFlisterStub(p.zbus) + storageClient := stubs.NewStorageModuleStub(p.zbus) tenantNS := fmt.Sprintf("ns%s", reservation.User) containerID := reservation.ID @@ -131,7 +128,7 @@ func containerProvisionImpl(ctx context.Context, reservation *Reservation) (Cont } for k, v := range config.SecretEnv { - v, err := decryptSecret(client, v) + v, err := decryptSecret(p.zbus, v) if err != nil { return ContainerResult{}, errors.Wrapf(err, "failed to decrypt secret env var '%s'", k) } @@ -141,7 +138,7 @@ func containerProvisionImpl(ctx context.Context, reservation *Reservation) (Cont var mounts []pkg.MountInfo for _, mount := range config.Mounts { var owner string - owner, err = cache.OwnerOf(mount.VolumeID) + owner, err = p.cache.OwnerOf(mount.VolumeID) if err != nil { return ContainerResult{}, errors.Wrapf(err, "failed to retrieve the owner of volume %s", mount.VolumeID) } @@ -231,7 +228,7 @@ func containerProvisionImpl(ctx context.Context, reservation *Reservation) (Cont } if config.Network.PublicIP6 { - join.IPv6, err = getIfaceIP(ctx, "pub", join.Namespace) + join.IPv6, err = p.getIfaceIP(ctx, "pub", join.Namespace) if err != nil { return ContainerResult{}, errors.Wrap(err, "error reading container ipv6") } @@ -245,12 +242,10 @@ func containerProvisionImpl(ctx context.Context, reservation *Reservation) (Cont }, nil } -func containerDecommission(ctx context.Context, reservation *Reservation) error { - client := GetZBus(ctx) - - container := stubs.NewContainerModuleStub(client) - flist := stubs.NewFlisterStub(client) - networkMgr := stubs.NewNetworkerStub(client) +func (p *Provisioner) containerDecommission(ctx context.Context, reservation *Reservation) error { + container := stubs.NewContainerModuleStub(p.zbus) + flist := stubs.NewFlisterStub(p.zbus) + networkMgr := stubs.NewNetworkerStub(p.zbus) tenantNS := fmt.Sprintf("ns%s", reservation.User) containerID := pkg.ContainerID(reservation.ID) diff --git a/pkg/provision/debug.go b/pkg/provision/debug.go index b4014cba0..307d82bf6 100644 --- a/pkg/provision/debug.go +++ b/pkg/provision/debug.go @@ -21,22 +21,22 @@ type Debug struct { Channel string `json:"channel"` } -func debugProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { +func (p *Provisioner) debugProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { var cfg Debug if err := json.Unmarshal(reservation.Data, &cfg); err != nil { return nil, err } - _, err := startZLF(ctx, reservation.ID, cfg) + _, err := p.startZLF(ctx, reservation.ID, cfg) // nothing to return to BCDB return nil, err } -func debugDecommission(ctx context.Context, reservation *Reservation) error { - return stopZLF(ctx, reservation.ID) +func (p *Provisioner) debugDecommission(ctx context.Context, reservation *Reservation) error { + return p.stopZLF(ctx, reservation.ID) } -func startZLF(ctx context.Context, ID string, cfg Debug) (string, error) { +func (p *Provisioner) startZLF(ctx context.Context, ID string, cfg Debug) (string, error) { zbus := GetZBus(ctx) identity := stubs.NewIdentityManagerStub(zbus) @@ -75,7 +75,7 @@ func startZLF(ctx context.Context, ID string, cfg Debug) (string, error) { return name, nil } -func stopZLF(ctx context.Context, ID string) error { +func (p *Provisioner) stopZLF(ctx context.Context, ID string) error { z, err := zinit.New("") if err != nil { return errors.Wrap(err, "fail to connect to zinit") diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go index 511c8123d..06d0a32dc 100644 --- a/pkg/provision/engine.go +++ b/pkg/provision/engine.go @@ -63,15 +63,7 @@ type EngineOps struct { // one reservation at a time. On error, the engine will log the error. and // continue to next reservation. func New(opts EngineOps) *Engine { - if opts.Provisioners == nil { - opts.Provisioners = provisioners - } - - if opts.Decomissioners == nil { - opts.Decomissioners = decommissioners - } - - e := &Engine{ + return &Engine{ nodeID: opts.NodeID, source: opts.Source, cache: opts.Cache, @@ -79,8 +71,6 @@ func New(opts EngineOps) *Engine { decomissioners: opts.Decomissioners, signer: opts.Signer, } - - return e } // Run starts processing reservation resource. Then try to allocate diff --git a/pkg/provision/kubernetes.go b/pkg/provision/kubernetes.go index da5526531..9a9dd7b63 100644 --- a/pkg/provision/kubernetes.go +++ b/pkg/provision/kubernetes.go @@ -47,18 +47,16 @@ type Kubernetes struct { const k3osFlistURL = "https://hub.grid.tf/tf-official-apps/k3os.flist" -func kubernetesProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { - return kubernetesProvisionImpl(ctx, reservation) +func (p *Provisioner) kubernetesProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { + return p.kubernetesProvisionImpl(ctx, reservation) } -func kubernetesProvisionImpl(ctx context.Context, reservation *Reservation) (result KubernetesResult, err error) { +func (p *Provisioner) kubernetesProvisionImpl(ctx context.Context, reservation *Reservation) (result KubernetesResult, err error) { var ( - client = GetZBus(ctx) - - storage = stubs.NewVDiskModuleStub(client) - network = stubs.NewNetworkerStub(client) - flist = stubs.NewFlisterStub(client) - vm = stubs.NewVMModuleStub(client) + storage = stubs.NewVDiskModuleStub(p.zbus) + network = stubs.NewNetworkerStub(p.zbus) + flist = stubs.NewFlisterStub(p.zbus) + vm = stubs.NewVMModuleStub(p.zbus) config Kubernetes @@ -72,7 +70,7 @@ func kubernetesProvisionImpl(ctx context.Context, reservation *Reservation) (res result.ID = reservation.ID result.IP = config.IP.String() - config.PlainClusterSecret, err = decryptSecret(client, config.ClusterSecret) + config.PlainClusterSecret, err = decryptSecret(p.zbus, config.ClusterSecret) if err != nil { return result, errors.Wrap(err, "failed to decrypt namespace password") } @@ -136,23 +134,23 @@ func kubernetesProvisionImpl(ctx context.Context, reservation *Reservation) (res }() var netInfo pkg.VMNetworkInfo - netInfo, err = buildNetworkInfo(ctx, reservation.User, iface, config) + netInfo, err = p.buildNetworkInfo(ctx, reservation.User, iface, config) if err != nil { return result, errors.Wrap(err, "could not generate network info") } if needsInstall { - if err = kubernetesInstall(ctx, reservation.ID, cpu, memory, diskPath, imagePath, netInfo, config); err != nil { + if err = p.kubernetesInstall(ctx, reservation.ID, cpu, memory, diskPath, imagePath, netInfo, config); err != nil { return result, errors.Wrap(err, "failed to install k3s") } } - err = kubernetesRun(ctx, reservation.ID, cpu, memory, diskPath, imagePath, netInfo, config) + err = p.kubernetesRun(ctx, reservation.ID, cpu, memory, diskPath, imagePath, netInfo, config) return result, err } -func kubernetesInstall(ctx context.Context, name string, cpu uint8, memory uint64, diskPath string, imagePath string, networkInfo pkg.VMNetworkInfo, cfg Kubernetes) error { - vm := stubs.NewVMModuleStub(GetZBus(ctx)) +func (p *Provisioner) kubernetesInstall(ctx context.Context, name string, cpu uint8, memory uint64, diskPath string, imagePath string, networkInfo pkg.VMNetworkInfo, cfg Kubernetes) error { + vm := stubs.NewVMModuleStub(p.zbus) cmdline := fmt.Sprintf("console=ttyS0 reboot=k panic=1 k3os.mode=install k3os.install.silent k3os.install.device=/dev/vda k3os.token=%s", cfg.PlainClusterSecret) // if there is no server url configured, the node is set up as a master, therefore @@ -213,8 +211,8 @@ func kubernetesInstall(ctx context.Context, name string, cpu uint8, memory uint6 return vm.Delete(name) } -func kubernetesRun(ctx context.Context, name string, cpu uint8, memory uint64, diskPath string, imagePath string, networkInfo pkg.VMNetworkInfo, cfg Kubernetes) error { - vm := stubs.NewVMModuleStub(GetZBus(ctx)) +func (p *Provisioner) kubernetesRun(ctx context.Context, name string, cpu uint8, memory uint64, diskPath string, imagePath string, networkInfo pkg.VMNetworkInfo, cfg Kubernetes) error { + vm := stubs.NewVMModuleStub(p.zbus) disks := make([]pkg.VMDisk, 1) // installed disk @@ -234,14 +232,12 @@ func kubernetesRun(ctx context.Context, name string, cpu uint8, memory uint64, d return vm.Run(kubevm) } -func kubernetesDecomission(ctx context.Context, reservation *Reservation) error { +func (p *Provisioner) kubernetesDecomission(ctx context.Context, reservation *Reservation) error { var ( - client = GetZBus(ctx) - - storage = stubs.NewVDiskModuleStub(client) - network = stubs.NewNetworkerStub(client) - flist = stubs.NewFlisterStub(client) - vm = stubs.NewVMModuleStub(client) + storage = stubs.NewVDiskModuleStub(p.zbus) + network = stubs.NewNetworkerStub(p.zbus) + flist = stubs.NewFlisterStub(p.zbus) + vm = stubs.NewVMModuleStub(p.zbus) cfg Kubernetes ) @@ -272,8 +268,8 @@ func kubernetesDecomission(ctx context.Context, reservation *Reservation) error return nil } -func buildNetworkInfo(ctx context.Context, userID string, iface string, cfg Kubernetes) (pkg.VMNetworkInfo, error) { - network := stubs.NewNetworkerStub(GetZBus(ctx)) +func (p *Provisioner) buildNetworkInfo(ctx context.Context, userID string, iface string, cfg Kubernetes) (pkg.VMNetworkInfo, error) { + network := stubs.NewNetworkerStub(p.zbus) netID := networkID(userID, string(cfg.NetworkID)) subnet, err := network.GetSubnet(netID) diff --git a/pkg/provision/midleware.go b/pkg/provision/midleware.go index e60f853db..19a3e7785 100644 --- a/pkg/provision/midleware.go +++ b/pkg/provision/midleware.go @@ -46,29 +46,3 @@ func GetOwnerCache(ctx context.Context) OwnerCache { return value.(OwnerCache) } - -// ZDBMapping interface -type ZDBMapping interface { - - // Get returns the container ID where namespace lives - // if the namespace is not found an empty string and false is returned - Get(namespace string) (string, bool) - - // Set saves the mapping between the namespace and a container ID - Set(namespace, container string) -} - -// WithZDBMapping set ZDBMapping into the context -func WithZDBMapping(ctx context.Context, mapping ZDBMapping) context.Context { - return context.WithValue(ctx, zdbMappingKey{}, mapping) -} - -// GetZDBMapping gets the zdb mapping from the context -func GetZDBMapping(ctx context.Context) ZDBMapping { - value := ctx.Value(zdbMappingKey{}) - if value == nil { - panic("no reservation mapping associated with context") - } - - return value.(ZDBMapping) -} diff --git a/pkg/provision/network.go b/pkg/provision/network.go index 4d427c720..6e7590b65 100644 --- a/pkg/provision/network.go +++ b/pkg/provision/network.go @@ -17,7 +17,7 @@ import ( ) // networkProvision is entry point to provision a network -func networkProvisionImpl(ctx context.Context, reservation *Reservation) error { +func (p *Provisioner) networkProvisionImpl(ctx context.Context, reservation *Reservation) error { network := &pkg.Network{} if err := json.Unmarshal(reservation.Data, network); err != nil { return errors.Wrap(err, "failed to unmarshal network from reservation") @@ -25,7 +25,7 @@ func networkProvisionImpl(ctx context.Context, reservation *Reservation) error { network.NetID = networkID(reservation.User, network.Name) - mgr := stubs.NewNetworkerStub(GetZBus(ctx)) + mgr := stubs.NewNetworkerStub(p.zbus) log.Debug().Str("network", fmt.Sprintf("%+v", network)).Msg("provision network") _, err := mgr.CreateNR(*network) @@ -36,12 +36,12 @@ func networkProvisionImpl(ctx context.Context, reservation *Reservation) error { return nil } -func networkProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { - return nil, networkProvisionImpl(ctx, reservation) +func (p *Provisioner) networkProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { + return nil, p.networkProvisionImpl(ctx, reservation) } -func networkDecommission(ctx context.Context, reservation *Reservation) error { - mgr := stubs.NewNetworkerStub(GetZBus(ctx)) +func (p *Provisioner) networkDecommission(ctx context.Context, reservation *Reservation) error { + mgr := stubs.NewNetworkerStub(p.zbus) network := &pkg.Network{} if err := json.Unmarshal(reservation.Data, network); err != nil { diff --git a/pkg/provision/provision.go b/pkg/provision/provision.go index ef21dc13d..79602fc40 100644 --- a/pkg/provision/provision.go +++ b/pkg/provision/provision.go @@ -9,6 +9,8 @@ package provision import ( "context" + + "github.com/threefoldtech/zbus" ) // ReservationSource interface. The source @@ -22,25 +24,42 @@ type ReservationSource interface { type ProvisionerFunc func(ctx context.Context, reservation *Reservation) (interface{}, error) type DecommissionerFunc func(ctx context.Context, reservation *Reservation) error -var ( - // provisioners defines the entry point for the different - // reservation provisioners. Currently only containers are - // supported. - provisioners = map[ReservationType]ProvisionerFunc{ - ContainerReservation: containerProvision, - VolumeReservation: volumeProvision, - NetworkReservation: networkProvision, - ZDBReservation: zdbProvision, - DebugReservation: debugProvision, - KubernetesReservation: kubernetesProvision, - } +type Provisioner struct { + cache OwnerCache + zbus zbus.Client - decommissioners = map[ReservationType]DecommissionerFunc{ - ContainerReservation: containerDecommission, - VolumeReservation: volumeDecommission, - NetworkReservation: networkDecommission, - ZDBReservation: zdbDecommission, - DebugReservation: debugDecommission, - KubernetesReservation: kubernetesDecomission, + Provisioners map[ReservationType]ProvisionerFunc + Decommissioners map[ReservationType]DecommissionerFunc +} + +func NewProvisioner(owerCache OwnerCache, zbus zbus.Client) *Provisioner { + p := &Provisioner{ + cache: owerCache, + zbus: zbus, + } + p.Provisioners = map[ReservationType]ProvisionerFunc{ + ContainerReservation: p.containerProvision, + VolumeReservation: p.volumeProvision, + NetworkReservation: p.networkProvision, + ZDBReservation: p.zdbProvision, + DebugReservation: p.debugProvision, + KubernetesReservation: p.kubernetesProvision, } + p.Decommissioners = map[ReservationType]DecommissionerFunc{ + ContainerReservation: p.containerDecommission, + VolumeReservation: p.volumeDecommission, + NetworkReservation: p.networkDecommission, + ZDBReservation: p.zdbDecommission, + DebugReservation: p.debugDecommission, + KubernetesReservation: p.kubernetesDecomission, + } + + return p +} + +var ( +// provisioners defines the entry point for the different +// reservation provisioners. Currently only containers are +// supported. + ) diff --git a/pkg/provision/volume.go b/pkg/provision/volume.go index 58fd4b54d..597c7b5e6 100644 --- a/pkg/provision/volume.go +++ b/pkg/provision/volume.go @@ -39,14 +39,13 @@ type VolumeResult struct { ID string `json:"volume_id"` } -func volumeProvisionImpl(ctx context.Context, reservation *Reservation) (VolumeResult, error) { - client := GetZBus(ctx) +func (p *Provisioner) volumeProvisionImpl(ctx context.Context, reservation *Reservation) (VolumeResult, error) { var config Volume if err := json.Unmarshal(reservation.Data, &config); err != nil { return VolumeResult{}, err } - storageClient := stubs.NewStorageModuleStub(client) + storageClient := stubs.NewStorageModuleStub(p.zbus) _, err := storageClient.Path(reservation.ID) if err == nil { @@ -64,13 +63,12 @@ func volumeProvisionImpl(ctx context.Context, reservation *Reservation) (VolumeR } // VolumeProvision is entry point to provision a volume -func volumeProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { - return volumeProvisionImpl(ctx, reservation) +func (p *Provisioner) volumeProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { + return p.volumeProvisionImpl(ctx, reservation) } -func volumeDecommission(ctx context.Context, reservation *Reservation) error { - client := GetZBus(ctx) - storageClient := stubs.NewStorageModuleStub(client) +func (p *Provisioner) volumeDecommission(ctx context.Context, reservation *Reservation) error { + storageClient := stubs.NewStorageModuleStub(p.zbus) return storageClient.ReleaseFilesystem(reservation.ID) } diff --git a/pkg/provision/zdb.go b/pkg/provision/zdb.go index 0b63e57dc..c89d16d5c 100644 --- a/pkg/provision/zdb.go +++ b/pkg/provision/zdb.go @@ -46,14 +46,13 @@ type ZDBResult struct { Port uint } -func zdbProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { - return zdbProvisionImpl(ctx, reservation) +func (p *Provisioner) zdbProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { + return p.zdbProvisionImpl(ctx, reservation) } -func zdbProvisionImpl(ctx context.Context, reservation *Reservation) (ZDBResult, error) { +func (p *Provisioner) zdbProvisionImpl(ctx context.Context, reservation *Reservation) (ZDBResult, error) { var ( - client = GetZBus(ctx) - storage = stubs.NewZDBAllocaterStub(client) + storage = stubs.NewZDBAllocaterStub(p.zbus) nsID = reservation.ID config ZDB @@ -64,7 +63,7 @@ func zdbProvisionImpl(ctx context.Context, reservation *Reservation) (ZDBResult, } var err error - config.PlainPassword, err = decryptSecret(client, config.Password) + config.PlainPassword, err = decryptSecret(p.zbus, config.Password) if err != nil { return ZDBResult{}, errors.Wrap(err, "failed to decrypt namespace password") } @@ -78,18 +77,18 @@ func zdbProvisionImpl(ctx context.Context, reservation *Reservation) (ZDBResult, containerID := pkg.ContainerID(allocation.VolumeID) - cont, err := ensureZdbContainer(ctx, allocation, config.Mode) + cont, err := p.ensureZdbContainer(ctx, allocation, config.Mode) if err != nil { return ZDBResult{}, err } - containerIP, err = getIfaceIP(ctx, nwmod.ZDBIface, cont.Network.Namespace) + containerIP, err = p.getIfaceIP(ctx, nwmod.ZDBIface, cont.Network.Namespace) if err != nil { return ZDBResult{}, err } // this call will actually configure the namespace in zdb and set the password - if err := createZDBNamespace(containerID, nsID, config); err != nil { + if err := p.createZDBNamespace(containerID, nsID, config); err != nil { return ZDBResult{}, err } @@ -100,18 +99,15 @@ func zdbProvisionImpl(ctx context.Context, reservation *Reservation) (ZDBResult, }, nil } -func ensureZdbContainer(ctx context.Context, allocation pkg.Allocation, mode pkg.ZDBMode) (pkg.Container, error) { - var ( - client = GetZBus(ctx) - container = stubs.NewContainerModuleStub(client) - ) +func (p *Provisioner) ensureZdbContainer(ctx context.Context, allocation pkg.Allocation, mode pkg.ZDBMode) (pkg.Container, error) { + var container = stubs.NewContainerModuleStub(p.zbus) name := pkg.ContainerID(allocation.VolumeID) cont, err := container.Inspect(zdbContainerNS, name) if err != nil && strings.Contains(err.Error(), "not found") { // container not found, create one - if err := createZdbContainer(ctx, allocation, mode); err != nil { + if err := p.createZdbContainer(ctx, allocation, mode); err != nil { return cont, err } cont, err = container.Inspect(zdbContainerNS, name) @@ -127,16 +123,13 @@ func ensureZdbContainer(ctx context.Context, allocation pkg.Allocation, mode pkg } -func createZdbContainer(ctx context.Context, allocation pkg.Allocation, mode pkg.ZDBMode) error { +func (p *Provisioner) createZdbContainer(ctx context.Context, allocation pkg.Allocation, mode pkg.ZDBMode) error { var ( name = pkg.ContainerID(allocation.VolumeID) volumePath = allocation.VolumePath - - client = GetZBus(ctx) - - cont = stubs.NewContainerModuleStub(client) - flist = stubs.NewFlisterStub(client) - network = stubs.NewNetworkerStub(client) + cont = stubs.NewContainerModuleStub(p.zbus) + flist = stubs.NewFlisterStub(p.zbus) + network = stubs.NewNetworkerStub(p.zbus) slog = log.With().Str("containerID", string(name)).Logger() ) @@ -220,11 +213,8 @@ func createZdbContainer(ctx context.Context, allocation pkg.Allocation, mode pkg return nil } -func getIfaceIP(ctx context.Context, ifaceName, namespace string) (containerIP net.IP, err error) { - var ( - client = GetZBus(ctx) - network = stubs.NewNetworkerStub(client) - ) +func (p *Provisioner) getIfaceIP(ctx context.Context, ifaceName, namespace string) (containerIP net.IP, err error) { + var network = stubs.NewNetworkerStub(p.zbus) getIP := func() error { ips, err := network.Addrs(ifaceName, namespace) @@ -259,7 +249,7 @@ func getIfaceIP(ctx context.Context, ifaceName, namespace string) (containerIP n return containerIP, nil } -func createZDBNamespace(containerID pkg.ContainerID, nsID string, config ZDB) error { +func (p *Provisioner) createZDBNamespace(containerID pkg.ContainerID, nsID string, config ZDB) error { zdbCl := zdbConnection(containerID) defer zdbCl.Close() if err := zdbCl.Connect(); err != nil { @@ -293,10 +283,9 @@ func createZDBNamespace(containerID pkg.ContainerID, nsID string, config ZDB) er return nil } -func zdbDecommission(ctx context.Context, reservation *Reservation) error { +func (p *Provisioner) zdbDecommission(ctx context.Context, reservation *Reservation) error { var ( - client = GetZBus(ctx) - storage = stubs.NewZDBAllocaterStub(client) + storage = stubs.NewZDBAllocaterStub(p.zbus) config ZDB nsID = reservation.ID @@ -313,7 +302,7 @@ func zdbDecommission(ctx context.Context, reservation *Reservation) error { return err } - _, err = ensureZdbContainer(ctx, allocation, config.Mode) + _, err = p.ensureZdbContainer(ctx, allocation, config.Mode) if err != nil { return errors.Wrap(err, "failed to find namespace zdb container") } From e6bd02d8a7dd44a7deae282dd96aa7f40b4f6e6d Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Sun, 19 Apr 2020 13:51:29 +0200 Subject: [PATCH 05/14] extract provision logic form zos node into its own package this leaves the provision package as a generic package that can be used by any other party to implement new primitives type and provisioning logic --- cmds/provisiond/main.go | 21 +- pkg/provision/crypto.go | 18 -- pkg/provision/doc.go | 8 + pkg/provision/engine.go | 68 +++-- pkg/provision/explorer/feedback.go | 38 +++ pkg/provision/explorer/source.go | 54 ++++ pkg/provision/feedback.go | 98 ------- pkg/provision/interface.go | 58 +++++ pkg/provision/midleware.go | 48 ---- pkg/provision/owner_cache.go | 70 ----- pkg/provision/{ => primitives}/container.go | 16 +- .../{ => primitives}/container_test.go | 2 +- pkg/provision/{ => primitives}/converter.go | 41 ++- .../{ => primitives}/converter_test.go | 4 +- pkg/provision/primitives/counters.go | 232 +++++++++++++++++ pkg/provision/primitives/crypto.go | 23 ++ pkg/provision/{ => primitives}/debug.go | 10 +- .../{ => primitives}/expiration_test.go | 2 +- pkg/provision/{ => primitives}/kubernetes.go | 9 +- pkg/provision/{ => primitives}/local_store.go | 158 +++-------- .../{ => primitives}/local_store_test.go | 2 +- pkg/provision/{ => primitives}/network.go | 9 +- .../{ => primitives}/network_test.go | 2 +- pkg/provision/primitives/order.go | 29 +++ .../{ => primitives}/provision_test.go | 2 +- pkg/provision/primitives/provisioner.go | 39 +++ .../primitives/resource_units_test.go | 235 +++++++++++++++++ pkg/provision/{ => primitives}/volume.go | 9 +- pkg/provision/{ => primitives}/volume_test.go | 2 +- pkg/provision/{ => primitives}/zdb.go | 9 +- pkg/provision/{ => primitives}/zdb_test.go | 2 +- pkg/provision/provision.go | 65 ----- pkg/provision/reservation.go | 200 -------------- pkg/provision/resource_units.go | 129 +-------- pkg/provision/resource_units_test.go | 245 ------------------ pkg/provision/source.go | 69 +---- 36 files changed, 893 insertions(+), 1133 deletions(-) create mode 100644 pkg/provision/doc.go create mode 100644 pkg/provision/explorer/feedback.go create mode 100644 pkg/provision/explorer/source.go delete mode 100644 pkg/provision/feedback.go create mode 100644 pkg/provision/interface.go delete mode 100644 pkg/provision/midleware.go delete mode 100644 pkg/provision/owner_cache.go rename pkg/provision/{ => primitives}/container.go (95%) rename pkg/provision/{ => primitives}/container_test.go (99%) rename pkg/provision/{ => primitives}/converter.go (85%) rename pkg/provision/{ => primitives}/converter_test.go (99%) create mode 100644 pkg/provision/primitives/counters.go create mode 100644 pkg/provision/primitives/crypto.go rename pkg/provision/{ => primitives}/debug.go (92%) rename pkg/provision/{ => primitives}/expiration_test.go (97%) rename pkg/provision/{ => primitives}/kubernetes.go (97%) rename pkg/provision/{ => primitives}/local_store.go (65%) rename pkg/provision/{ => primitives}/local_store_test.go (98%) rename pkg/provision/{ => primitives}/network.go (89%) rename pkg/provision/{ => primitives}/network_test.go (99%) create mode 100644 pkg/provision/primitives/order.go rename pkg/provision/{ => primitives}/provision_test.go (98%) create mode 100644 pkg/provision/primitives/provisioner.go create mode 100644 pkg/provision/primitives/resource_units_test.go rename pkg/provision/{ => primitives}/volume.go (88%) rename pkg/provision/{ => primitives}/volume_test.go (99%) rename pkg/provision/{ => primitives}/zdb.go (97%) rename pkg/provision/{ => primitives}/zdb_test.go (99%) delete mode 100644 pkg/provision/provision.go delete mode 100644 pkg/provision/resource_units_test.go diff --git a/cmds/provisiond/main.go b/cmds/provisiond/main.go index e3c7aa92e..884995743 100644 --- a/cmds/provisiond/main.go +++ b/cmds/provisiond/main.go @@ -10,6 +10,8 @@ import ( "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/app" "github.com/threefoldtech/zos/pkg/environment" + "github.com/threefoldtech/zos/pkg/provision/explorer" + "github.com/threefoldtech/zos/pkg/provision/primitives" "github.com/threefoldtech/zos/pkg/stubs" "github.com/threefoldtech/zos/pkg/utils" @@ -94,28 +96,32 @@ func main() { if err != nil { log.Fatal().Err(err).Msg("failed to instantiate BCDB client") } + + // keep track of resource unnits reserved and amount of workloads provisionned + statser := &primitives.Counters{} + // to store reservation locally on the node - localStore, err := provision.NewFSStore(filepath.Join(storageDir, "reservations")) + localStore, err := primitives.NewFSStore(filepath.Join(storageDir, "reservations")) if err != nil { log.Fatal().Err(err).Msg("failed to create local reservation store") } + // update stats from the local reservation cache + localStore.Sync(statser) - provisioner := provision.NewProvisioner( - // use to get the user ID of a reservation - provision.NewCache(localStore, provision.ReservationGetterFromWorkloads(cl.Workloads)), - zbusCl) + provisioner := primitives.NewProvisioner(localStore, zbusCl) engine := provision.New(provision.EngineOps{ NodeID: nodeID.Identity(), Cache: localStore, Source: provision.CombinedSource( - provision.PollSource(provision.ReservationPollerFromWorkloads(cl.Workloads, provision.WorkloadToProvisionType), nodeID), + provision.PollSource(explorer.ReservationPollerFromWorkloads(cl.Workloads, primitives.WorkloadToProvisionType, primitives.ProvisionOrder), nodeID), provision.NewDecommissionSource(localStore), ), Provisioners: provisioner.Provisioners, Decomissioners: provisioner.Decommissioners, - Feedback: provision.NewExplorerFeedback(cl, provision.ToSchemaType), + Feedback: explorer.NewExplorerFeedback(cl, primitives.ResultToSchemaType), Signer: identity, + Statser: statser, }) server.Register(zbus.ObjectID{Name: module, Version: "0.0.1"}, pkg.ProvisionMonitor(engine)) @@ -144,7 +150,6 @@ func main() { } type store interface { - provision.ReservationGetter provision.ReservationPoller provision.Feedbacker } diff --git a/pkg/provision/crypto.go b/pkg/provision/crypto.go index c1fa587fd..78a56b298 100644 --- a/pkg/provision/crypto.go +++ b/pkg/provision/crypto.go @@ -2,14 +2,11 @@ package provision import ( "bytes" - "encoding/hex" "github.com/pkg/errors" - "github.com/threefoldtech/zbus" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/crypto" - "github.com/threefoldtech/zos/pkg/stubs" "golang.org/x/crypto/ed25519" ) @@ -76,18 +73,3 @@ func Verify(r *Reservation) error { return crypto.Verify(publicKey, buf.Bytes(), r.Signature) } - -func decryptSecret(client zbus.Client, secret string) (string, error) { - if len(secret) == 0 { - return "", nil - } - identity := stubs.NewIdentityManagerStub(client) - - bytes, err := hex.DecodeString(secret) - if err != nil { - return "", err - } - - out, err := identity.Decrypt(bytes) - return string(out), err -} diff --git a/pkg/provision/doc.go b/pkg/provision/doc.go new file mode 100644 index 000000000..2bd9ab8df --- /dev/null +++ b/pkg/provision/doc.go @@ -0,0 +1,8 @@ +// Package provision is a daemon that pulls +// on reservation source, and then tries to +// apply these reservations locally. +// Note that, provision module doesn't expose +// any interface on zbus. since it should not +// be driven by users, instead all reservation +// should be pushed by the reservation source. +package provision diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go index 06d0a32dc..4e993cb2b 100644 --- a/pkg/provision/engine.go +++ b/pkg/provision/engine.go @@ -13,37 +13,15 @@ import ( "github.com/rs/zerolog/log" ) -// ReservationCache define the interface to store -// some reservations -type ReservationCache interface { - Add(r *Reservation) error - Get(id string) (*Reservation, error) - Remove(id string) error - Exists(id string) (bool, error) - Counters() Counters -} - -// Feedbacker defines the method that needs to be implemented -// to send the provision result to BCDB -type Feedbacker interface { - Feedback(nodeID string, r *Result) error - Deleted(nodeID, id string) error - UpdateReservedResources(nodeID string, c Counters) error -} - -type Signer interface { - Sign(b []byte) ([]byte, error) -} - type Engine struct { - nodeID string - source ReservationSource - cache ReservationCache - feedback Feedbacker - // cl *client.Client + nodeID string + source ReservationSource + cache ReservationCache + feedback Feedbacker provisioners map[ReservationType]ProvisionerFunc - decomissioners map[ReservationType]DecommissionerFunc + decomissioners map[ReservationType]DecomissionerFunc signer Signer + statser Statser } type EngineOps struct { @@ -52,8 +30,9 @@ type EngineOps struct { Cache ReservationCache Feedback Feedbacker Provisioners map[ReservationType]ProvisionerFunc - Decomissioners map[ReservationType]DecommissionerFunc + Decomissioners map[ReservationType]DecomissionerFunc Signer Signer + Statser Statser } // New creates a new engine. Once started, the engine @@ -70,6 +49,7 @@ func New(opts EngineOps) *Engine { feedback: opts.Feedback, decomissioners: opts.Decomissioners, signer: opts.Signer, + statser: opts.Statser, } } @@ -118,7 +98,7 @@ func (e *Engine) Run(ctx context.Context) error { } } - if err := e.feedback.UpdateReservedResources(e.nodeID, e.cache.Counters()); err != nil { + if err := e.updateStats(); err != nil { log.Error().Err(err).Msg("failed to updated the capacity counters") } @@ -239,6 +219,16 @@ func (e *Engine) reply(ctx context.Context, r *Reservation, err error, info inte return e.feedback.Feedback(e.nodeID, result) } +func (e *Engine) updateStats() error { + wl := e.statser.CurrentWorkloads() + r := e.statser.CurrentUnits() + + log.Info().Msgf("reserved resource %+v", r) + log.Info().Msgf("provisionned workloads %+v", wl) + + return e.feedback.UpdateStats(e.nodeID, wl, r) +} + func (e *Engine) Counters(ctx context.Context) <-chan pkg.ProvisionCounters { ch := make(chan pkg.ProvisionCounters) go func() { @@ -248,18 +238,18 @@ func (e *Engine) Counters(ctx context.Context) <-chan pkg.ProvisionCounters { case <-ctx.Done(): } - c := e.cache.Counters() - pc := pkg.ProvisionCounters{ - Container: int64(c.containers.Current()), - Network: int64(c.networks.Current()), - ZDB: int64(c.zdbs.Current()), - Volume: int64(c.volumes.Current()), - VM: int64(c.vms.Current()), - } + // c := e.cache.Counters() + // pc := pkg.ProvisionCounters{ + // Container: int64(c.containers.Current()), + // Network: int64(c.networks.Current()), + // ZDB: int64(c.zdbs.Current()), + // Volume: int64(c.volumes.Current()), + // VM: int64(c.vms.Current()), + // } select { case <-ctx.Done(): - case ch <- pc: + // case ch <- pc: } } }() diff --git a/pkg/provision/explorer/feedback.go b/pkg/provision/explorer/feedback.go new file mode 100644 index 000000000..7aef82121 --- /dev/null +++ b/pkg/provision/explorer/feedback.go @@ -0,0 +1,38 @@ +package explorer + +import ( + "fmt" + + "github.com/threefoldtech/tfexplorer/client" + "github.com/threefoldtech/tfexplorer/models/generated/directory" + "github.com/threefoldtech/zos/pkg/provision" +) + +type ExplorerFeedback struct { + client *client.Client + converter provision.ResultConverterFunc +} + +func NewExplorerFeedback(client *client.Client, converter provision.ResultConverterFunc) *ExplorerFeedback { + return &ExplorerFeedback{ + client: client, + converter: converter, + } +} + +func (e *ExplorerFeedback) Feedback(nodeID string, r *provision.Result) error { + wr, err := e.converter(*r) + if err != nil { + return fmt.Errorf("failed to convert result into schema type: %w") + } + + return e.client.Workloads.WorkloadPutResult(nodeID, r.ID, *wr) +} + +func (e *ExplorerFeedback) Deleted(nodeID, id string) error { + return e.client.Workloads.WorkloadPutDeleted(nodeID, id) +} + +func (e *ExplorerFeedback) UpdateStats(nodeID string, w directory.WorkloadAmount, u directory.ResourceAmount) error { + return e.client.Directory.NodeUpdateUsedResources(nodeID, u, w) +} diff --git a/pkg/provision/explorer/source.go b/pkg/provision/explorer/source.go new file mode 100644 index 000000000..0687b92ac --- /dev/null +++ b/pkg/provision/explorer/source.go @@ -0,0 +1,54 @@ +package explorer + +import ( + "fmt" + "sort" + + "github.com/rs/zerolog/log" + "github.com/threefoldtech/tfexplorer/client" + "github.com/threefoldtech/zos/pkg" + "github.com/threefoldtech/zos/pkg/provision" +) + +type ReservationPoller struct { + wl client.Workloads + inputConv provision.ReservationConverterFunc + provisionOrder map[provision.ReservationType]int +} + +func (r *ReservationPoller) Poll(nodeID pkg.Identifier, from uint64) ([]*provision.Reservation, error) { + + list, err := r.wl.Workloads(nodeID.Identity(), from) + if err != nil { + return nil, fmt.Errorf("error while retrieving workloads from explorer: %w", err) + } + + result := make([]*provision.Reservation, 0, len(list)) + for _, wl := range list { + log.Info().Msgf("convert %+v", wl) + r, err := r.inputConv(wl) + if err != nil { + return nil, err + } + + result = append(result, r) + } + + if r.provisionOrder != nil { + // sorts the workloads in the oder they need to be processed by provisiond + sort.Slice(result, func(i int, j int) bool { + return r.provisionOrder[result[i].Type] < r.provisionOrder[result[j].Type] + }) + } + + return result, nil +} + +// ReservationPollerFromWorkloads returns a reservation poller from client.Workloads +func ReservationPollerFromWorkloads(wl client.Workloads, inputConv provision.ReservationConverterFunc, provisionOrder map[provision.ReservationType]int) *ReservationPoller { + return &ReservationPoller{ + wl: wl, + inputConv: inputConv, + provisionOrder: provisionOrder, + } +} diff --git a/pkg/provision/feedback.go b/pkg/provision/feedback.go deleted file mode 100644 index d008af926..000000000 --- a/pkg/provision/feedback.go +++ /dev/null @@ -1,98 +0,0 @@ -package provision - -import ( - "fmt" - - "github.com/rs/zerolog/log" - "github.com/threefoldtech/tfexplorer/client" - "github.com/threefoldtech/tfexplorer/models/generated/directory" - "github.com/threefoldtech/tfexplorer/models/generated/workloads" - "github.com/threefoldtech/tfexplorer/schema" -) - -type ExplorerFeedback struct { - client *client.Client - converter ResultConverterFunc -} - -func NewExplorerFeedback(client *client.Client, converter ResultConverterFunc) *ExplorerFeedback { - if converter == nil { - converter = ToSchemaType - } - return &ExplorerFeedback{ - client: client, - converter: converter, - } -} - -func (e *ExplorerFeedback) Feedback(nodeID string, r *Result) error { - wr, err := e.converter(*r) - if err != nil { - return fmt.Errorf("failed to convert result into schema type: %w") - } - - return e.client.Workloads.WorkloadPutResult(nodeID, r.ID, *wr) -} - -func (e *ExplorerFeedback) Deleted(nodeID, id string) error { - return e.client.Workloads.WorkloadPutDeleted(nodeID, id) -} - -func (e *ExplorerFeedback) UpdateReservedResources(nodeID string, c Counters) error { - resources := directory.ResourceAmount{ - Cru: c.CRU.Current(), - Mru: float64(c.MRU.Current()) / float64(gib), - Sru: float64(c.SRU.Current()) / float64(gib), - Hru: float64(c.HRU.Current()) / float64(gib), - } - - workloads := directory.WorkloadAmount{ - Volume: uint16(c.volumes.Current()), - Container: uint16(c.containers.Current()), - ZDBNamespace: uint16(c.zdbs.Current()), - K8sVM: uint16(c.vms.Current()), - Network: uint16(c.networks.Current()), - } - log.Info().Msgf("reserved resource %+v", resources) - log.Info().Msgf("provisionned workloads %+v", workloads) - return e.client.Directory.NodeUpdateUsedResources(nodeID, resources, workloads) -} - -// ToSchemaType converts result to schema type -func ToSchemaType(r Result) (*workloads.Result, error) { - var rType workloads.ResultCategoryEnum - switch r.Type { - case VolumeReservation: - rType = workloads.ResultCategoryVolume - case ContainerReservation: - rType = workloads.ResultCategoryContainer - case ZDBReservation: - rType = workloads.ResultCategoryZDB - case NetworkReservation: - rType = workloads.ResultCategoryNetwork - case KubernetesReservation: - rType = workloads.ResultCategoryK8S - // case ResultCategoryProxy: - // rType = workloads.ResultCategoryProxy - // case ResultCategoryReverseProxy: - // rType = workloads.ResultCategoryReverseProxy, - // case ResultCategorySubDomain: - // rType = workloads.ResultCategorySubDomain, - // case ResultCategoryDomainDelegate: - // rType = workloads.ResultCategoryDomainDelegate, - default: - return nil, fmt.Errorf("unknown reservation type: %s", r.Type) - } - - result := workloads.Result{ - Category: rType, - WorkloadId: r.ID, - DataJson: r.Data, - Signature: r.Signature, - State: workloads.ResultStateEnum(r.State), - Message: r.Error, - Epoch: schema.Date{Time: r.Created}, - } - - return &result, nil -} diff --git a/pkg/provision/interface.go b/pkg/provision/interface.go new file mode 100644 index 000000000..1f6d50024 --- /dev/null +++ b/pkg/provision/interface.go @@ -0,0 +1,58 @@ +package provision + +import ( + "context" + + "github.com/threefoldtech/tfexplorer/models/generated/directory" + "github.com/threefoldtech/tfexplorer/models/generated/workloads" +) + +// ReservationSource interface. The source +// defines how the node will get reservation requests +// then reservations are applied to the node to deploy +// a resource of the given Reservation.Type +type ReservationSource interface { + Reservations(ctx context.Context) <-chan *Reservation +} + +// ProvisionerFunc is the function called by the Engine to provision a workload +type ProvisionerFunc func(ctx context.Context, reservation *Reservation) (interface{}, error) + +// DecomissionerFunc is the function called by the Engine to decomission a workload +type DecomissionerFunc func(ctx context.Context, reservation *Reservation) error + +//ResultConverterFunc is used to convert internal Result type to the explorer workload result +type ResultConverterFunc func(result Result) (*workloads.Result, error) + +// ReservationCache define the interface to store +// some reservations +type ReservationCache interface { + Add(r *Reservation) error + Get(id string) (*Reservation, error) + Remove(id string) error + Exists(id string) (bool, error) +} + +// Feedbacker defines the method that needs to be implemented +// to send the provision result to BCDB +type Feedbacker interface { + Feedback(nodeID string, r *Result) error + Deleted(nodeID, id string) error + UpdateStats(nodeID string, w directory.WorkloadAmount, u directory.ResourceAmount) error +} + +// Signer interface is used to sign reservation result before +// sending them to the explorer +type Signer interface { + Sign(b []byte) ([]byte, error) +} + +// Statser is used by the provision Engine to keep +// track of how much resource unit and number of primitives +// is provisionned +type Statser interface { + Increment(r *Reservation) error + Decrement(r *Reservation) error + CurrentUnits() directory.ResourceAmount + CurrentWorkloads() directory.WorkloadAmount +} diff --git a/pkg/provision/midleware.go b/pkg/provision/midleware.go deleted file mode 100644 index 19a3e7785..000000000 --- a/pkg/provision/midleware.go +++ /dev/null @@ -1,48 +0,0 @@ -package provision - -import ( - "context" - - "github.com/threefoldtech/zbus" -) - -type ( - zbusKey struct{} - owerCacheKey struct{} - zdbMappingKey struct{} -) - -// WithZBus adds a zbus client middleware to context -func WithZBus(ctx context.Context, client zbus.Client) context.Context { - return context.WithValue(ctx, zbusKey{}, client) -} - -// GetZBus gets a zbus client from context -func GetZBus(ctx context.Context) zbus.Client { - value := ctx.Value(zbusKey{}) - if value == nil { - panic("no tnodb middleware associated with context") - } - - return value.(zbus.Client) -} - -// OwnerCache interface -type OwnerCache interface { - OwnerOf(reservationID string) (string, error) -} - -// WithOwnerCache adds the owner cache to context -func WithOwnerCache(ctx context.Context, cache OwnerCache) context.Context { - return context.WithValue(ctx, owerCacheKey{}, cache) -} - -// GetOwnerCache gets the owner cache from context -func GetOwnerCache(ctx context.Context) OwnerCache { - value := ctx.Value(owerCacheKey{}) - if value == nil { - panic("no reservation cache associated with context") - } - - return value.(OwnerCache) -} diff --git a/pkg/provision/owner_cache.go b/pkg/provision/owner_cache.go deleted file mode 100644 index 926a8ec9f..000000000 --- a/pkg/provision/owner_cache.go +++ /dev/null @@ -1,70 +0,0 @@ -package provision - -import ( - "fmt" - - "github.com/threefoldtech/tfexplorer/client" -) - -// ReservationGetter define the interface how to get -// a reservation from its ID -type ReservationGetter interface { - Get(id string) (*Reservation, error) -} - -type reservationGetter struct { - wl client.Workloads -} - -func (r *reservationGetter) Get(id string) (*Reservation, error) { - l, err := r.wl.WorkloadGet(id) - if err != nil { - return nil, err - } - - return WorkloadToProvisionType(l) -} - -// ReservationGetterFromWorkloads get a reservation getter from the client.Workloads interface -func ReservationGetterFromWorkloads(wl client.Workloads) ReservationGetter { - return &reservationGetter{wl: wl} -} - -// ownerCache allows to get the user ID of owner of a reservation -type ownerCache struct { - local ReservationGetter - remote ReservationGetter -} - -// NewCache returns a new initialized reservation cache -func NewCache(local, remote ReservationGetter) OwnerCache { - return &ownerCache{ - local: local, - remote: remote, - } -} - -// OwnerOf return the userID of the creator of the reservation -// identified by reservationID -func (c *ownerCache) OwnerOf(reservationID string) (string, error) { - var ( - r *Reservation - err error - ) - - for _, source := range []ReservationGetter{c.local, c.remote} { - if source == nil { - continue - } - r, err = c.local.Get(reservationID) - if err == nil { - break - } - } - - if r == nil { - return "", fmt.Errorf("failed to get owner of reservation %s", reservationID) - } - - return r.User, nil -} diff --git a/pkg/provision/container.go b/pkg/provision/primitives/container.go similarity index 95% rename from pkg/provision/container.go rename to pkg/provision/primitives/container.go index 40f61656c..703339f1a 100644 --- a/pkg/provision/container.go +++ b/pkg/provision/primitives/container.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "context" @@ -14,6 +14,7 @@ import ( "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/container/logger" "github.com/threefoldtech/zos/pkg/container/stats" + "github.com/threefoldtech/zos/pkg/provision" "github.com/threefoldtech/zos/pkg/stubs" ) @@ -75,12 +76,12 @@ type ContainerCapacity struct { Memory uint64 `json:"memory"` } -func (p *Provisioner) containerProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { +func (p *Provisioner) containerProvision(ctx context.Context, reservation *provision.Reservation) (interface{}, error) { return p.containerProvisionImpl(ctx, reservation) } // ContainerProvision is entry point to container reservation -func (p *Provisioner) containerProvisionImpl(ctx context.Context, reservation *Reservation) (ContainerResult, error) { +func (p *Provisioner) containerProvisionImpl(ctx context.Context, reservation *provision.Reservation) (ContainerResult, error) { containerClient := stubs.NewContainerModuleStub(p.zbus) flistClient := stubs.NewFlisterStub(p.zbus) storageClient := stubs.NewStorageModuleStub(p.zbus) @@ -137,13 +138,12 @@ func (p *Provisioner) containerProvisionImpl(ctx context.Context, reservation *R var mounts []pkg.MountInfo for _, mount := range config.Mounts { - var owner string - owner, err = p.cache.OwnerOf(mount.VolumeID) + volumeRes, err := p.cache.Get(mount.VolumeID) if err != nil { return ContainerResult{}, errors.Wrapf(err, "failed to retrieve the owner of volume %s", mount.VolumeID) } - if owner != reservation.User { + if volumeRes.User != reservation.User { return ContainerResult{}, fmt.Errorf("cannot use volume %s, user %s is not the owner of it", mount.VolumeID, reservation.User) } @@ -174,7 +174,7 @@ func (p *Provisioner) containerProvisionImpl(ctx context.Context, reservation *R Str("config", fmt.Sprintf("%+v", config)). Msg("deploying network") - networkMgr := stubs.NewNetworkerStub(GetZBus(ctx)) + networkMgr := stubs.NewNetworkerStub(p.zbus) ips := make([]string, len(config.Network.IPs)) for i, ip := range config.Network.IPs { @@ -242,7 +242,7 @@ func (p *Provisioner) containerProvisionImpl(ctx context.Context, reservation *R }, nil } -func (p *Provisioner) containerDecommission(ctx context.Context, reservation *Reservation) error { +func (p *Provisioner) containerDecommission(ctx context.Context, reservation *provision.Reservation) error { container := stubs.NewContainerModuleStub(p.zbus) flist := stubs.NewFlisterStub(p.zbus) networkMgr := stubs.NewNetworkerStub(p.zbus) diff --git a/pkg/provision/container_test.go b/pkg/provision/primitives/container_test.go similarity index 99% rename from pkg/provision/container_test.go rename to pkg/provision/primitives/container_test.go index 9a8a08ff4..796feb163 100644 --- a/pkg/provision/container_test.go +++ b/pkg/provision/primitives/container_test.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "context" diff --git a/pkg/provision/converter.go b/pkg/provision/primitives/converter.go similarity index 85% rename from pkg/provision/converter.go rename to pkg/provision/primitives/converter.go index 587127185..69e17cfd0 100644 --- a/pkg/provision/converter.go +++ b/pkg/provision/primitives/converter.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "encoding/json" @@ -7,10 +7,12 @@ import ( "time" "github.com/threefoldtech/tfexplorer/models/generated/workloads" + "github.com/threefoldtech/tfexplorer/schema" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/container/logger" "github.com/threefoldtech/zos/pkg/container/stats" "github.com/threefoldtech/zos/pkg/network/types" + "github.com/threefoldtech/zos/pkg/provision" ) // ContainerToProvisionType converts TfgridReservationContainer1 to Container @@ -204,11 +206,11 @@ func NetResourceToProvisionType(r workloads.NetworkNetResource) (pkg.NetResource } // WorkloadToProvisionType TfgridReservationWorkload1 to provision.Reservation -func WorkloadToProvisionType(w workloads.ReservationWorkload) (*Reservation, error) { - reservation := &Reservation{ +func WorkloadToProvisionType(w workloads.ReservationWorkload) (*provision.Reservation, error) { + reservation := &provision.Reservation{ ID: w.WorkloadId, User: w.User, - Type: ReservationType(w.Type.String()), + Type: provision.ReservationType(w.Type.String()), Created: w.Created.Time, Duration: time.Duration(w.Duration) * time.Second, Signature: []byte(w.Signature), @@ -258,3 +260,34 @@ func WorkloadToProvisionType(w workloads.ReservationWorkload) (*Reservation, err return reservation, nil } + +// ResultToSchemaType converts result to schema type +func ResultToSchemaType(r provision.Result) (*workloads.Result, error) { + + var rType workloads.ResultCategoryEnum + switch r.Type { + case VolumeReservation: + rType = workloads.ResultCategoryVolume + case ContainerReservation: + rType = workloads.ResultCategoryContainer + case ZDBReservation: + rType = workloads.ResultCategoryZDB + case NetworkReservation: + rType = workloads.ResultCategoryNetwork + case KubernetesReservation: + default: + return nil, fmt.Errorf("unknown reservation type: %s", r.Type) + } + + result := workloads.Result{ + Category: rType, + WorkloadId: r.ID, + DataJson: r.Data, + Signature: r.Signature, + State: workloads.ResultStateEnum(r.State), + Message: r.Error, + Epoch: schema.Date{Time: r.Created}, + } + + return &result, nil +} diff --git a/pkg/provision/converter_test.go b/pkg/provision/primitives/converter_test.go similarity index 99% rename from pkg/provision/converter_test.go rename to pkg/provision/primitives/converter_test.go index 7e6e2f1cb..7f1244266 100644 --- a/pkg/provision/converter_test.go +++ b/pkg/provision/primitives/converter_test.go @@ -1,4 +1,4 @@ -package provision_test +package primitives_test import ( "encoding/json" @@ -11,10 +11,10 @@ import ( "github.com/threefoldtech/zos/pkg" "github.com/stretchr/testify/require" + schema "github.com/threefoldtech/tfexplorer/schema" "github.com/threefoldtech/zos/pkg/container/logger" "github.com/threefoldtech/zos/pkg/container/stats" "github.com/threefoldtech/zos/pkg/provision" - schema "github.com/threefoldtech/tfexplorer/schema" "gotest.tools/assert" ) diff --git a/pkg/provision/primitives/counters.go b/pkg/provision/primitives/counters.go new file mode 100644 index 000000000..9675072f8 --- /dev/null +++ b/pkg/provision/primitives/counters.go @@ -0,0 +1,232 @@ +package primitives + +import ( + "encoding/json" + "fmt" + "sync/atomic" + + "github.com/threefoldtech/tfexplorer/models/generated/directory" + "github.com/threefoldtech/zos/pkg/provision" +) + +// Counter interface +type Counter interface { + // Increment counter atomically by v + Increment(v uint64) uint64 + // Decrement counter atomically by v + Decrement(v uint64) uint64 + // Current returns the current value + Current() uint64 +} + +type counterNop struct{} + +func (c *counterNop) Increment(v uint64) uint64 { + return 0 +} + +func (c *counterNop) Decrement(v uint64) uint64 { + return 0 +} + +func (c *counterNop) Current() uint64 { + return 0 +} + +// counterImpl value for safe increment/decrement +type counterImpl uint64 + +// Increment counter atomically by one +func (c *counterImpl) Increment(v uint64) uint64 { + return atomic.AddUint64((*uint64)(c), v) +} + +// Decrement counter atomically by one +func (c *counterImpl) Decrement(v uint64) uint64 { + return atomic.AddUint64((*uint64)(c), -v) +} + +// Current returns the current value +func (c *counterImpl) Current() uint64 { + return atomic.LoadUint64((*uint64)(c)) +} + +// Counters tracks the amount of primitives workload deployed and +// the amount of resource unit used +type Counters struct { + containers counterImpl + volumes counterImpl + networks counterImpl + zdbs counterImpl + vms counterImpl + debugs counterImpl + + SRU counterImpl // SSD storage in bytes + HRU counterImpl // HDD storage in bytes + MRU counterImpl // Memory storage in bytes + CRU counterImpl // CPU count absolute +} + +func (c *Counters) CurrentWorkloads() directory.WorkloadAmount { + return directory.WorkloadAmount{ + Network: uint16(c.containers.Current()), + Volume: uint16(c.volumes.Current()), + ZDBNamespace: uint16(c.networks.Current()), + Container: uint16(c.zdbs.Current()), + K8sVM: uint16(c.vms.Current()), + } +} + +func (c *Counters) CurrentUnits() directory.ResourceAmount { + return directory.ResourceAmount{ + Cru: c.CRU.Current(), + Mru: float64(c.CRU.Current()), + Hru: float64(c.CRU.Current()), + Sru: float64(c.CRU.Current()), + } +} + +const ( + mib = uint64(1024 * 1024) + gib = uint64(mib * 1024) +) + +func (c *Counters) Increment(r *provision.Reservation) error { + + var ( + u resourceUnits + err error + ) + + switch r.Type { + case VolumeReservation: + u, err = processVolume(r) + case ContainerReservation: + u, err = processContainer(r) + case ZDBReservation: + u, err = processZdb(r) + case KubernetesReservation: + c.vms.Increment(1) + u, err = processKubernetes(r) + } + if err != nil { + return err + } + + c.CRU.Increment(u.CRU) + c.MRU.Increment(u.MRU) + c.SRU.Increment(u.SRU) + c.HRU.Increment(u.HRU) + + return nil +} + +func (c *Counters) Decrement(r *provision.Reservation) error { + + var ( + u resourceUnits + err error + ) + + switch r.Type { + case VolumeReservation: + c.volumes.Decrement(1) + u, err = processVolume(r) + case ContainerReservation: + c.volumes.Decrement(1) + u, err = processContainer(r) + case ZDBReservation: + c.zdbs.Decrement(1) + u, err = processZdb(r) + case KubernetesReservation: + c.vms.Decrement(1) + u, err = processKubernetes(r) + } + if err != nil { + return err + } + + c.CRU.Decrement(u.CRU) + c.MRU.Decrement(u.MRU) + c.SRU.Decrement(u.SRU) + c.HRU.Decrement(u.HRU) + + return nil +} + +type resourceUnits struct { + SRU uint64 `json:"sru,omitempty"` + HRU uint64 `json:"hru,omitempty"` + MRU uint64 `json:"mru,omitempty"` + CRU uint64 `json:"cru,omitempty"` +} + +func processVolume(r *provision.Reservation) (u resourceUnits, err error) { + var volume Volume + if err = json.Unmarshal(r.Data, &volume); err != nil { + return u, err + } + + // volume.size and SRU is in GiB + switch volume.Type { + case SSDDiskType: + u.SRU = volume.Size * gib + case HDDDiskType: + u.HRU = volume.Size * gib + } + + return u, nil +} + +func processContainer(r *provision.Reservation) (u resourceUnits, err error) { + var cont Container + if err = json.Unmarshal(r.Data, &cont); err != nil { + return u, err + } + u.CRU = uint64(cont.Capacity.CPU) + // memory is in MiB + u.MRU = cont.Capacity.Memory * mib + u.SRU = 256 * mib // 250MiB are allocated on SSD for the root filesystem used by the flist + + return u, nil +} + +func processZdb(r *provision.Reservation) (u resourceUnits, err error) { + if r.Type != ZDBReservation { + return u, fmt.Errorf("wrong type or reservation %s, excepted %s", r.Type, ZDBReservation) + } + var zdbVolume ZDB + if err := json.Unmarshal(r.Data, &zdbVolume); err != nil { + return u, err + } + + switch zdbVolume.DiskType { + case "SSD": + u.SRU = zdbVolume.Size * gib + case "HDD": + u.HRU = zdbVolume.Size * gib + } + + return u, nil +} + +func processKubernetes(r *provision.Reservation) (u resourceUnits, err error) { + var k8s Kubernetes + if err = json.Unmarshal(r.Data, &k8s); err != nil { + return u, err + } + + // size are defined at https://github.com/threefoldtech/zos/blob/master/pkg/provision/kubernetes.go#L311 + switch k8s.Size { + case 1: + u.CRU = 1 + u.MRU = 2 * gib + u.SRU = 50 * gib + case 2: + u.CRU = 2 + u.MRU = 4 * gib + u.SRU = 100 * gib + } + + return u, nil +} diff --git a/pkg/provision/primitives/crypto.go b/pkg/provision/primitives/crypto.go new file mode 100644 index 000000000..faa76cc16 --- /dev/null +++ b/pkg/provision/primitives/crypto.go @@ -0,0 +1,23 @@ +package primitives + +import ( + "encoding/hex" + + "github.com/threefoldtech/zbus" + "github.com/threefoldtech/zos/pkg/stubs" +) + +func decryptSecret(client zbus.Client, secret string) (string, error) { + if len(secret) == 0 { + return "", nil + } + identity := stubs.NewIdentityManagerStub(client) + + bytes, err := hex.DecodeString(secret) + if err != nil { + return "", err + } + + out, err := identity.Decrypt(bytes) + return string(out), err +} diff --git a/pkg/provision/debug.go b/pkg/provision/primitives/debug.go similarity index 92% rename from pkg/provision/debug.go rename to pkg/provision/primitives/debug.go index 307d82bf6..e00bf1685 100644 --- a/pkg/provision/debug.go +++ b/pkg/provision/primitives/debug.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "context" @@ -8,6 +8,7 @@ import ( "strings" "github.com/rs/zerolog/log" + "github.com/threefoldtech/zos/pkg/provision" "github.com/threefoldtech/zos/pkg/stubs" "github.com/threefoldtech/zos/pkg/zinit" @@ -21,7 +22,7 @@ type Debug struct { Channel string `json:"channel"` } -func (p *Provisioner) debugProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { +func (p *Provisioner) debugProvision(ctx context.Context, reservation *provision.Reservation) (interface{}, error) { var cfg Debug if err := json.Unmarshal(reservation.Data, &cfg); err != nil { return nil, err @@ -32,13 +33,12 @@ func (p *Provisioner) debugProvision(ctx context.Context, reservation *Reservati return nil, err } -func (p *Provisioner) debugDecommission(ctx context.Context, reservation *Reservation) error { +func (p *Provisioner) debugDecommission(ctx context.Context, reservation *provision.Reservation) error { return p.stopZLF(ctx, reservation.ID) } func (p *Provisioner) startZLF(ctx context.Context, ID string, cfg Debug) (string, error) { - zbus := GetZBus(ctx) - identity := stubs.NewIdentityManagerStub(zbus) + identity := stubs.NewIdentityManagerStub(p.zbus) path, err := exec.LookPath("zlf") if err != nil { diff --git a/pkg/provision/expiration_test.go b/pkg/provision/primitives/expiration_test.go similarity index 97% rename from pkg/provision/expiration_test.go rename to pkg/provision/primitives/expiration_test.go index 3053664cc..033004a28 100644 --- a/pkg/provision/expiration_test.go +++ b/pkg/provision/primitives/expiration_test.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "testing" diff --git a/pkg/provision/kubernetes.go b/pkg/provision/primitives/kubernetes.go similarity index 97% rename from pkg/provision/kubernetes.go rename to pkg/provision/primitives/kubernetes.go index 9a9dd7b63..a9940b12f 100644 --- a/pkg/provision/kubernetes.go +++ b/pkg/provision/primitives/kubernetes.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "context" @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/threefoldtech/zos/pkg" + "github.com/threefoldtech/zos/pkg/provision" "github.com/threefoldtech/zos/pkg/stubs" ) @@ -47,11 +48,11 @@ type Kubernetes struct { const k3osFlistURL = "https://hub.grid.tf/tf-official-apps/k3os.flist" -func (p *Provisioner) kubernetesProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { +func (p *Provisioner) kubernetesProvision(ctx context.Context, reservation *provision.Reservation) (interface{}, error) { return p.kubernetesProvisionImpl(ctx, reservation) } -func (p *Provisioner) kubernetesProvisionImpl(ctx context.Context, reservation *Reservation) (result KubernetesResult, err error) { +func (p *Provisioner) kubernetesProvisionImpl(ctx context.Context, reservation *provision.Reservation) (result KubernetesResult, err error) { var ( storage = stubs.NewVDiskModuleStub(p.zbus) network = stubs.NewNetworkerStub(p.zbus) @@ -232,7 +233,7 @@ func (p *Provisioner) kubernetesRun(ctx context.Context, name string, cpu uint8, return vm.Run(kubevm) } -func (p *Provisioner) kubernetesDecomission(ctx context.Context, reservation *Reservation) error { +func (p *Provisioner) kubernetesDecomission(ctx context.Context, reservation *provision.Reservation) error { var ( storage = stubs.NewVDiskModuleStub(p.zbus) network = stubs.NewNetworkerStub(p.zbus) diff --git a/pkg/provision/local_store.go b/pkg/provision/primitives/local_store.go similarity index 65% rename from pkg/provision/local_store.go rename to pkg/provision/primitives/local_store.go index 844cff799..ff62059f6 100644 --- a/pkg/provision/local_store.go +++ b/pkg/provision/primitives/local_store.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "encoding/json" @@ -8,82 +8,28 @@ import ( "path" "path/filepath" "sync" - "sync/atomic" "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/threefoldtech/zos/pkg/app" + "github.com/threefoldtech/zos/pkg/provision" "github.com/threefoldtech/zos/pkg/versioned" ) -// Counter interface -type Counter interface { - // Increment counter atomically by v - Increment(v uint64) uint64 - // Decrement counter atomically by v - Decrement(v uint64) uint64 - // Current returns the current value - Current() uint64 -} - -type counterNop struct{} - -func (c *counterNop) Increment(v uint64) uint64 { - return 0 -} - -func (c *counterNop) Decrement(v uint64) uint64 { - return 0 -} - -func (c *counterNop) Current() uint64 { - return 0 -} - -// counterImpl value for safe increment/decrement -type counterImpl uint64 - -// Increment counter atomically by one -func (c *counterImpl) Increment(v uint64) uint64 { - return atomic.AddUint64((*uint64)(c), v) -} - -// Decrement counter atomically by one -func (c *counterImpl) Decrement(v uint64) uint64 { - return atomic.AddUint64((*uint64)(c), -v) -} +var ( + // reservationSchemaV1 reservation schema version 1 + reservationSchemaV1 = versioned.MustParse("1.0.0") + // ReservationSchemaLastVersion link to latest version + reservationSchemaLastVersion = reservationSchemaV1 +) -// Current returns the current value -func (c *counterImpl) Current() uint64 { - return atomic.LoadUint64((*uint64)(c)) +// FSStore is a in reservation store +// using the filesystem as backend +type FSStore struct { + sync.RWMutex + root string } -type ( - // FSStore is a in reservation store - // using the filesystem as backend - FSStore struct { - sync.RWMutex - root string - counters Counters - } - - // Counters tracks the amount of primitives workload deployed and - // the amount of resource unit used - Counters struct { - containers counterImpl - volumes counterImpl - networks counterImpl - zdbs counterImpl - vms counterImpl - debugs counterImpl - - SRU counterImpl // SSD storage in bytes - HRU counterImpl // HDD storage in bytes - MRU counterImpl // Memory storage in bytes - CRU counterImpl // CPU count absolute - } -) - // NewFSStore creates a in memory reservation store func NewFSStore(root string) (*FSStore, error) { store := &FSStore{ @@ -106,7 +52,7 @@ func NewFSStore(root string) (*FSStore, error) { log.Info().Msg("restart detected, keep reservation cache intact") - return store, store.sync() + return store, nil } //TODO: i think both sync and removeAllButPersistent can be merged into @@ -150,7 +96,7 @@ func (s *FSStore) removeAllButPersistent(rootPath string) error { return nil } -func (s *FSStore) sync() error { +func (s *FSStore) Sync(statser provision.Statser) error { s.RLock() defer s.RUnlock() @@ -169,40 +115,14 @@ func (s *FSStore) sync() error { return err } - s.counterFor(r.Type).Increment(1) - s.processResourceUnits(r, true) + statser.Increment(r) } return nil } -// Counters returns stats about the cashed reservations -func (s *FSStore) Counters() Counters { - return s.counters -} - -func (s *FSStore) counterFor(typ ReservationType) Counter { - switch typ { - case ContainerReservation: - return &s.counters.containers - case VolumeReservation: - return &s.counters.volumes - case NetworkReservation: - return &s.counters.networks - case ZDBReservation: - return &s.counters.zdbs - case DebugReservation: - return &s.counters.debugs - case KubernetesReservation: - return &s.counters.vms - default: - // this will avoid nil pointer - return &counterNop{} - } -} - // Add a reservation to the store -func (s *FSStore) Add(r *Reservation) error { +func (s *FSStore) Add(r *provision.Reservation) error { s.Lock() defer s.Unlock() @@ -228,10 +148,10 @@ func (s *FSStore) Add(r *Reservation) error { return err } - s.counterFor(r.Type).Increment(1) - if err := s.processResourceUnits(r, true); err != nil { - return errors.Wrapf(err, "could not compute the amount of resource used by reservation %s", r.ID) - } + // s.counterFor(r.Type).Increment(1) + // if err := s.processResourceUnits(r, true); err != nil { + // return errors.Wrapf(err, "could not compute the amount of resource used by reservation %s", r.ID) + // } return nil } @@ -241,13 +161,13 @@ func (s *FSStore) Remove(id string) error { s.Lock() defer s.Unlock() - r, err := s.get(id) - if os.IsNotExist(errors.Cause(err)) { - return nil - } + // r, err := s.get(id) + // if os.IsNotExist(errors.Cause(err)) { + // return nil + // } path := filepath.Join(s.root, id) - err = os.Remove(path) + err := os.Remove(path) if os.IsNotExist(err) { // shouldn't happen because we just did a get return nil @@ -255,17 +175,17 @@ func (s *FSStore) Remove(id string) error { return err } - s.counterFor(r.Type).Decrement(1) - if err := s.processResourceUnits(r, false); err != nil { - return errors.Wrapf(err, "could not compute the amount of resource used by reservation %s", r.ID) - } + // s.counterFor(r.Type).Decrement(1) + // if err := s.processResourceUnits(r, false); err != nil { + // return errors.Wrapf(err, "could not compute the amount of resource used by reservation %s", r.ID) + // } return nil } // GetExpired returns all id the the reservations that are expired // at the time of the function call -func (s *FSStore) GetExpired() ([]*Reservation, error) { +func (s *FSStore) GetExpired() ([]*provision.Reservation, error) { s.RLock() defer s.RUnlock() @@ -274,7 +194,7 @@ func (s *FSStore) GetExpired() ([]*Reservation, error) { return nil, err } - rs := make([]*Reservation, 0, len(infos)) + rs := make([]*provision.Reservation, 0, len(infos)) for _, info := range infos { if info.IsDir() { continue @@ -293,7 +213,7 @@ func (s *FSStore) GetExpired() ([]*Reservation, error) { return nil, err } if r.Expired() { - r.Tag = Tag{"source": "FSStore"} + // r.Tag = Tag{"source": "FSStore"} rs = append(rs, r) } @@ -304,7 +224,7 @@ func (s *FSStore) GetExpired() ([]*Reservation, error) { // Get retrieves a specific reservation using its ID // if returns a non nil error if the reservation is not present in the store -func (s *FSStore) Get(id string) (*Reservation, error) { +func (s *FSStore) Get(id string) (*provision.Reservation, error) { s.RLock() defer s.RUnlock() @@ -313,9 +233,9 @@ func (s *FSStore) Get(id string) (*Reservation, error) { // getType retrieves a specific reservation's type using its ID // if returns a non nil error if the reservation is not present in the store -func (s *FSStore) getType(id string) (ReservationType, error) { +func (s *FSStore) getType(id string) (provision.ReservationType, error) { res := struct { - Type ReservationType `json:"type"` + Type provision.ReservationType `json:"type"` }{} path := filepath.Join(s.root, id) f, err := os.Open(path) @@ -362,7 +282,7 @@ func (s *FSStore) Exists(id string) (bool, error) { return false, err } -func (s *FSStore) get(id string) (*Reservation, error) { +func (s *FSStore) get(id string) (*provision.Reservation, error) { path := filepath.Join(s.root, id) f, err := os.Open(path) if os.IsNotExist(err) { @@ -381,7 +301,7 @@ func (s *FSStore) get(id string) (*Reservation, error) { } validV1 := versioned.MustParseRange(fmt.Sprintf("<=%s", reservationSchemaV1)) - var reservation Reservation + var reservation provision.Reservation if validV1(reader.Version()) { if err := json.NewDecoder(reader).Decode(&reservation); err != nil { @@ -390,7 +310,7 @@ func (s *FSStore) get(id string) (*Reservation, error) { } else { return nil, fmt.Errorf("unknown reservation object version (%s)", reader.Version()) } - reservation.Tag = Tag{"source": "FSStore"} + // reservation.Tag = Tag{"source": "FSStore"} return &reservation, nil } diff --git a/pkg/provision/local_store_test.go b/pkg/provision/primitives/local_store_test.go similarity index 98% rename from pkg/provision/local_store_test.go rename to pkg/provision/primitives/local_store_test.go index 24f6ae9d1..915ca512f 100644 --- a/pkg/provision/local_store_test.go +++ b/pkg/provision/primitives/local_store_test.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "io/ioutil" diff --git a/pkg/provision/network.go b/pkg/provision/primitives/network.go similarity index 89% rename from pkg/provision/network.go rename to pkg/provision/primitives/network.go index 6e7590b65..9a4bea5ac 100644 --- a/pkg/provision/network.go +++ b/pkg/provision/primitives/network.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "bytes" @@ -13,11 +13,12 @@ import ( "github.com/rs/zerolog/log" "github.com/threefoldtech/zos/pkg" + "github.com/threefoldtech/zos/pkg/provision" "github.com/threefoldtech/zos/pkg/stubs" ) // networkProvision is entry point to provision a network -func (p *Provisioner) networkProvisionImpl(ctx context.Context, reservation *Reservation) error { +func (p *Provisioner) networkProvisionImpl(ctx context.Context, reservation *provision.Reservation) error { network := &pkg.Network{} if err := json.Unmarshal(reservation.Data, network); err != nil { return errors.Wrap(err, "failed to unmarshal network from reservation") @@ -36,11 +37,11 @@ func (p *Provisioner) networkProvisionImpl(ctx context.Context, reservation *Res return nil } -func (p *Provisioner) networkProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { +func (p *Provisioner) networkProvision(ctx context.Context, reservation *provision.Reservation) (interface{}, error) { return nil, p.networkProvisionImpl(ctx, reservation) } -func (p *Provisioner) networkDecommission(ctx context.Context, reservation *Reservation) error { +func (p *Provisioner) networkDecommission(ctx context.Context, reservation *provision.Reservation) error { mgr := stubs.NewNetworkerStub(p.zbus) network := &pkg.Network{} diff --git a/pkg/provision/network_test.go b/pkg/provision/primitives/network_test.go similarity index 99% rename from pkg/provision/network_test.go rename to pkg/provision/primitives/network_test.go index 77eb82d34..7dc049977 100644 --- a/pkg/provision/network_test.go +++ b/pkg/provision/primitives/network_test.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "context" diff --git a/pkg/provision/primitives/order.go b/pkg/provision/primitives/order.go new file mode 100644 index 000000000..77715b0b6 --- /dev/null +++ b/pkg/provision/primitives/order.go @@ -0,0 +1,29 @@ +package primitives + +import "github.com/threefoldtech/zos/pkg/provision" + +const ( + // ContainerReservation type + ContainerReservation provision.ReservationType = "container" + // VolumeReservation type + VolumeReservation provision.ReservationType = "volume" + // NetworkReservation type + NetworkReservation provision.ReservationType = "network" + // ZDBReservation type + ZDBReservation provision.ReservationType = "zdb" + // DebugReservation type + DebugReservation provision.ReservationType = "debug" + // KubernetesReservation type + KubernetesReservation provision.ReservationType = "kubernetes" +) + +// ProvisionOrder is used to sort the workload type +// in the right order for provision engine +var ProvisionOrder = map[provision.ReservationType]int{ + DebugReservation: 0, + NetworkReservation: 1, + ZDBReservation: 2, + VolumeReservation: 3, + ContainerReservation: 4, + KubernetesReservation: 5, +} diff --git a/pkg/provision/provision_test.go b/pkg/provision/primitives/provision_test.go similarity index 98% rename from pkg/provision/provision_test.go rename to pkg/provision/primitives/provision_test.go index 5ea405343..ac22f075b 100644 --- a/pkg/provision/provision_test.go +++ b/pkg/provision/primitives/provision_test.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "context" diff --git a/pkg/provision/primitives/provisioner.go b/pkg/provision/primitives/provisioner.go new file mode 100644 index 000000000..b159f29f7 --- /dev/null +++ b/pkg/provision/primitives/provisioner.go @@ -0,0 +1,39 @@ +package primitives + +import ( + "github.com/threefoldtech/zbus" + "github.com/threefoldtech/zos/pkg/provision" +) + +type Provisioner struct { + cache provision.ReservationCache + zbus zbus.Client + + Provisioners map[provision.ReservationType]provision.ProvisionerFunc + Decommissioners map[provision.ReservationType]provision.DecomissionerFunc +} + +func NewProvisioner(cache provision.ReservationCache, zbus zbus.Client) *Provisioner { + p := &Provisioner{ + cache: cache, + zbus: zbus, + } + p.Provisioners = map[provision.ReservationType]provision.ProvisionerFunc{ + ContainerReservation: p.containerProvision, + VolumeReservation: p.volumeProvision, + NetworkReservation: p.networkProvision, + ZDBReservation: p.zdbProvision, + DebugReservation: p.debugProvision, + KubernetesReservation: p.kubernetesProvision, + } + p.Decommissioners = map[provision.ReservationType]provision.DecomissionerFunc{ + ContainerReservation: p.containerDecommission, + VolumeReservation: p.volumeDecommission, + NetworkReservation: p.networkDecommission, + ZDBReservation: p.zdbDecommission, + DebugReservation: p.debugDecommission, + KubernetesReservation: p.kubernetesDecomission, + } + + return p +} diff --git a/pkg/provision/primitives/resource_units_test.go b/pkg/provision/primitives/resource_units_test.go new file mode 100644 index 000000000..7cf5fc806 --- /dev/null +++ b/pkg/provision/primitives/resource_units_test.go @@ -0,0 +1,235 @@ +package primitives + +// func Test_processZDB(t *testing.T) { +// type args struct { +// r *Reservation +// } + +// tests := []struct { +// name string +// args args +// want resourceUnits +// wantErr bool +// }{ +// { +// name: "zdbSSD", +// args: args{ +// r: &Reservation{ +// Type: ZDBReservation, +// Data: mustMarshalJSON(t, ZDB{ +// Size: 1, +// DiskType: pkg.SSDDevice, +// }), +// }, +// }, +// want: resourceUnits{ +// SRU: 1 * gib, +// }, +// wantErr: false, +// }, +// { +// name: "zdbHDD", +// args: args{ +// r: &Reservation{ +// Type: ZDBReservation, +// Data: mustMarshalJSON(t, ZDB{ +// Size: 1, +// DiskType: pkg.HDDDevice, +// }), +// }, +// }, +// want: resourceUnits{ +// HRU: 1 * gib, +// }, +// wantErr: false, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// got, err := processZdb(tt.args.r) +// if tt.wantErr { +// assert.Error(t, err) +// } else { +// assert.Equal(t, tt.want, got) +// } +// }) +// } +// } + +// func Test_processVolume(t *testing.T) { +// type args struct { +// r *Reservation +// } + +// tests := []struct { +// name string +// args args +// wantU resourceUnits +// wantErr bool +// }{ +// { +// name: "volumeSSD", +// args: args{ +// r: &Reservation{ +// Type: VolumeReservation, +// Data: mustMarshalJSON(t, Volume{ +// Size: 1, +// Type: SSDDiskType, +// }), +// }, +// }, +// wantU: resourceUnits{ +// SRU: 1 * gib, +// }, +// }, +// { +// name: "volumeHDD", +// args: args{ +// r: &Reservation{ +// Type: VolumeReservation, +// Data: mustMarshalJSON(t, Volume{ +// Size: 1, +// Type: HDDDiskType, +// }), +// }, +// }, +// wantU: resourceUnits{ +// HRU: 1 * gib, +// }, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// gotU, err := processVolume(tt.args.r) +// if tt.wantErr { +// assert.Error(t, err) +// } else { +// assert.Equal(t, tt.wantU, gotU) +// } +// }) +// } +// } + +// func Test_processContainer(t *testing.T) { +// type args struct { +// r *Reservation +// } + +// tests := []struct { +// name string +// args args +// wantU resourceUnits +// wantErr bool +// }{ +// { +// name: "container", +// args: args{ +// r: &Reservation{ +// Type: VolumeReservation, +// Data: mustMarshalJSON(t, Container{ +// Capacity: ContainerCapacity{ +// CPU: 2, +// Memory: 1024, +// }, +// }), +// }, +// }, +// wantU: resourceUnits{ +// CRU: 2, +// MRU: 1 * gib, +// SRU: 256 * mib, +// }, +// }, +// { +// name: "container", +// args: args{ +// r: &Reservation{ +// Type: VolumeReservation, +// Data: mustMarshalJSON(t, Container{ +// Capacity: ContainerCapacity{ +// CPU: 2, +// Memory: 2048, +// }, +// }), +// }, +// }, +// wantU: resourceUnits{ +// CRU: 2, +// MRU: 2 * gib, +// SRU: 256 * mib, +// }, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// gotU, err := processContainer(tt.args.r) +// if tt.wantErr { +// assert.Error(t, err) +// } else { +// assert.Equal(t, tt.wantU, gotU) +// } +// }) +// } +// } + +// func Test_processKubernetes(t *testing.T) { +// type args struct { +// r *Reservation +// } + +// tests := []struct { +// name string +// args args +// wantU resourceUnits +// wantErr bool +// }{ +// { +// name: "k8sSize1", +// args: args{ +// r: &Reservation{ +// Type: KubernetesReservation, +// Data: mustMarshalJSON(t, Kubernetes{ +// Size: 1, +// }), +// }, +// }, +// wantU: resourceUnits{ +// CRU: 1, +// MRU: 2 * gib, +// SRU: 50 * gib, +// }, +// }, +// { +// name: "k8sSize2", +// args: args{ +// r: &Reservation{ +// Type: KubernetesReservation, +// Data: mustMarshalJSON(t, Kubernetes{ +// Size: 2, +// }), +// }, +// }, +// wantU: resourceUnits{ +// CRU: 2, +// MRU: 4 * gib, +// SRU: 100 * gib, +// }, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// gotU, err := processKubernetes(tt.args.r) +// if tt.wantErr { +// assert.Error(t, err) +// } else { +// assert.Equal(t, tt.wantU, gotU) +// } +// }) +// } +// } + +// func mustMarshalJSON(t *testing.T, v interface{}) []byte { +// b, err := json.Marshal(v) +// require.NoError(t, err) +// return b +// } diff --git a/pkg/provision/volume.go b/pkg/provision/primitives/volume.go similarity index 88% rename from pkg/provision/volume.go rename to pkg/provision/primitives/volume.go index 597c7b5e6..76a1f0ed4 100644 --- a/pkg/provision/volume.go +++ b/pkg/provision/primitives/volume.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "context" @@ -7,6 +7,7 @@ import ( "github.com/rs/zerolog/log" "github.com/threefoldtech/zos/pkg" + "github.com/threefoldtech/zos/pkg/provision" "github.com/threefoldtech/zos/pkg/stubs" ) @@ -39,7 +40,7 @@ type VolumeResult struct { ID string `json:"volume_id"` } -func (p *Provisioner) volumeProvisionImpl(ctx context.Context, reservation *Reservation) (VolumeResult, error) { +func (p *Provisioner) volumeProvisionImpl(ctx context.Context, reservation *provision.Reservation) (VolumeResult, error) { var config Volume if err := json.Unmarshal(reservation.Data, &config); err != nil { return VolumeResult{}, err @@ -63,11 +64,11 @@ func (p *Provisioner) volumeProvisionImpl(ctx context.Context, reservation *Rese } // VolumeProvision is entry point to provision a volume -func (p *Provisioner) volumeProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { +func (p *Provisioner) volumeProvision(ctx context.Context, reservation *provision.Reservation) (interface{}, error) { return p.volumeProvisionImpl(ctx, reservation) } -func (p *Provisioner) volumeDecommission(ctx context.Context, reservation *Reservation) error { +func (p *Provisioner) volumeDecommission(ctx context.Context, reservation *provision.Reservation) error { storageClient := stubs.NewStorageModuleStub(p.zbus) return storageClient.ReleaseFilesystem(reservation.ID) diff --git a/pkg/provision/volume_test.go b/pkg/provision/primitives/volume_test.go similarity index 99% rename from pkg/provision/volume_test.go rename to pkg/provision/primitives/volume_test.go index 9129c5eda..451b1fc8e 100644 --- a/pkg/provision/volume_test.go +++ b/pkg/provision/primitives/volume_test.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "context" diff --git a/pkg/provision/zdb.go b/pkg/provision/primitives/zdb.go similarity index 97% rename from pkg/provision/zdb.go rename to pkg/provision/primitives/zdb.go index c89d16d5c..c536b55d6 100644 --- a/pkg/provision/zdb.go +++ b/pkg/provision/primitives/zdb.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "context" @@ -11,6 +11,7 @@ import ( "github.com/cenkalti/backoff/v3" "github.com/threefoldtech/zos/pkg/network/ifaceutil" + "github.com/threefoldtech/zos/pkg/provision" "github.com/threefoldtech/zos/pkg/zdb" "github.com/pkg/errors" @@ -46,11 +47,11 @@ type ZDBResult struct { Port uint } -func (p *Provisioner) zdbProvision(ctx context.Context, reservation *Reservation) (interface{}, error) { +func (p *Provisioner) zdbProvision(ctx context.Context, reservation *provision.Reservation) (interface{}, error) { return p.zdbProvisionImpl(ctx, reservation) } -func (p *Provisioner) zdbProvisionImpl(ctx context.Context, reservation *Reservation) (ZDBResult, error) { +func (p *Provisioner) zdbProvisionImpl(ctx context.Context, reservation *provision.Reservation) (ZDBResult, error) { var ( storage = stubs.NewZDBAllocaterStub(p.zbus) @@ -283,7 +284,7 @@ func (p *Provisioner) createZDBNamespace(containerID pkg.ContainerID, nsID strin return nil } -func (p *Provisioner) zdbDecommission(ctx context.Context, reservation *Reservation) error { +func (p *Provisioner) zdbDecommission(ctx context.Context, reservation *provision.Reservation) error { var ( storage = stubs.NewZDBAllocaterStub(p.zbus) diff --git a/pkg/provision/zdb_test.go b/pkg/provision/primitives/zdb_test.go similarity index 99% rename from pkg/provision/zdb_test.go rename to pkg/provision/primitives/zdb_test.go index 6164d24f7..2fbd57171 100644 --- a/pkg/provision/zdb_test.go +++ b/pkg/provision/primitives/zdb_test.go @@ -1,4 +1,4 @@ -package provision +package primitives import ( "context" diff --git a/pkg/provision/provision.go b/pkg/provision/provision.go deleted file mode 100644 index 79602fc40..000000000 --- a/pkg/provision/provision.go +++ /dev/null @@ -1,65 +0,0 @@ -// Package provision is a daemon that pulls -// on reservation source, and then tries to -// apply these reservations locally. -// Note that, provision module doesn't expose -// any interface on zbus. since it should not -// be driven by users, instead all reservation -// should be pushed by the reservation source. -package provision - -import ( - "context" - - "github.com/threefoldtech/zbus" -) - -// ReservationSource interface. The source -// defines how the node will get reservation requests -// then reservations are applied to the node to deploy -// a resource of the given Reservation.Type -type ReservationSource interface { - Reservations(ctx context.Context) <-chan *Reservation -} - -type ProvisionerFunc func(ctx context.Context, reservation *Reservation) (interface{}, error) -type DecommissionerFunc func(ctx context.Context, reservation *Reservation) error - -type Provisioner struct { - cache OwnerCache - zbus zbus.Client - - Provisioners map[ReservationType]ProvisionerFunc - Decommissioners map[ReservationType]DecommissionerFunc -} - -func NewProvisioner(owerCache OwnerCache, zbus zbus.Client) *Provisioner { - p := &Provisioner{ - cache: owerCache, - zbus: zbus, - } - p.Provisioners = map[ReservationType]ProvisionerFunc{ - ContainerReservation: p.containerProvision, - VolumeReservation: p.volumeProvision, - NetworkReservation: p.networkProvision, - ZDBReservation: p.zdbProvision, - DebugReservation: p.debugProvision, - KubernetesReservation: p.kubernetesProvision, - } - p.Decommissioners = map[ReservationType]DecommissionerFunc{ - ContainerReservation: p.containerDecommission, - VolumeReservation: p.volumeDecommission, - NetworkReservation: p.networkDecommission, - ZDBReservation: p.zdbDecommission, - DebugReservation: p.debugDecommission, - KubernetesReservation: p.kubernetesDecomission, - } - - return p -} - -var ( -// provisioners defines the entry point for the different -// reservation provisioners. Currently only containers are -// supported. - -) diff --git a/pkg/provision/reservation.go b/pkg/provision/reservation.go index 4c0c1aa3d..3828a09cc 100644 --- a/pkg/provision/reservation.go +++ b/pkg/provision/reservation.go @@ -10,38 +10,11 @@ import ( "github.com/pkg/errors" "github.com/threefoldtech/tfexplorer/models/generated/workloads" - "github.com/threefoldtech/tfexplorer/schema" - "github.com/threefoldtech/zos/pkg" - "github.com/threefoldtech/zos/pkg/container/logger" - "github.com/threefoldtech/zos/pkg/container/stats" - "github.com/threefoldtech/zos/pkg/versioned" ) // ReservationType type type ReservationType string -const ( - // ContainerReservation type - ContainerReservation ReservationType = "container" - // VolumeReservation type - VolumeReservation ReservationType = "volume" - // NetworkReservation type - NetworkReservation ReservationType = "network" - // ZDBReservation type - ZDBReservation ReservationType = "zdb" - // DebugReservation type - DebugReservation ReservationType = "debug" - // KubernetesReservation type - KubernetesReservation ReservationType = "kubernetes" -) - -var ( - // reservationSchemaV1 reservation schema version 1 - reservationSchemaV1 = versioned.MustParse("1.0.0") - // reservationSchemaLastVersion link to latest version - reservationSchemaLastVersion = reservationSchemaV1 -) - // Reservation struct type Reservation struct { // ID of the reservation @@ -150,179 +123,6 @@ func (r *Reservation) validate() error { return nil } -func networkReservation(i interface{}) workloads.Network { - n := i.(pkg.Network) - network := workloads.Network{ - Name: n.Name, - Iprange: n.IPRange.ToSchema(), - WorkloadId: 1, - NetworkResources: make([]workloads.NetworkNetResource, len(n.NetResources)), - } - - for i, nr := range n.NetResources { - network.NetworkResources[i] = workloads.NetworkNetResource{ - NodeId: nr.NodeID, - Iprange: nr.Subnet.ToSchema(), - WireguardPrivateKeyEncrypted: nr.WGPrivateKey, - WireguardPublicKey: nr.WGPublicKey, - WireguardListenPort: int64(nr.WGListenPort), - Peers: make([]workloads.WireguardPeer, len(nr.Peers)), - } - - for y, peer := range nr.Peers { - network.NetworkResources[i].Peers[y] = workloads.WireguardPeer{ - Iprange: peer.Subnet.ToSchema(), - Endpoint: peer.Endpoint, - PublicKey: peer.WGPublicKey, - AllowedIprange: make([]schema.IPRange, len(peer.AllowedIPs)), - } - - for z, ip := range peer.AllowedIPs { - network.NetworkResources[i].Peers[y].AllowedIprange[z] = ip.ToSchema() - } - } - } - return network -} - -func containerReservation(i interface{}, nodeID string) workloads.Container { - - c := i.(Container) - container := workloads.Container{ - NodeId: nodeID, - WorkloadId: 1, - Flist: c.FList, - HubUrl: c.FlistStorage, - Environment: c.Env, - SecretEnvironment: c.SecretEnv, - Entrypoint: c.Entrypoint, - Interactive: c.Interactive, - Volumes: make([]workloads.ContainerMount, len(c.Mounts)), - StatsAggregator: make([]workloads.StatsAggregator, len(c.StatsAggregator)), - Logs: make([]workloads.Logs, len(c.Logs)), - NetworkConnection: []workloads.NetworkConnection{ - { - NetworkId: string(c.Network.NetworkID), - Ipaddress: c.Network.IPs[0], - PublicIp6: c.Network.PublicIP6, - }, - }, - Capacity: workloads.ContainerCapacity{ - Cpu: int64(c.Capacity.CPU), - Memory: int64(c.Capacity.Memory), - }, - } - - for i, v := range c.Mounts { - container.Volumes[i] = workloads.ContainerMount{ - VolumeId: v.VolumeID, - Mountpoint: v.Mountpoint, - } - } - - for i, l := range c.Logs { - if l.Type != logger.RedisType { - container.Logs[i] = workloads.Logs{ - Type: "unknown", - Data: workloads.LogsRedis{}, - } - - continue - } - - container.Logs[i] = workloads.Logs{ - Type: l.Type, - Data: workloads.LogsRedis{ - Stdout: l.Data.Stdout, - Stderr: l.Data.Stderr, - }, - } - } - - for i, s := range c.StatsAggregator { - if s.Type != stats.RedisType { - container.StatsAggregator[i] = workloads.StatsAggregator{ - Type: "unknown", - Data: workloads.StatsRedis{}, - } - - continue - } - - container.StatsAggregator[i] = workloads.StatsAggregator{ - Type: s.Type, - Data: workloads.StatsRedis{ - Endpoint: s.Data.Endpoint, - }, - } - } - - return container -} - -func volumeReservation(i interface{}, nodeID string) workloads.Volume { - v := i.(Volume) - - volume := workloads.Volume{ - NodeId: nodeID, - WorkloadId: 1, - Size: int64(v.Size), - } - - if v.Type == HDDDiskType { - volume.Type = workloads.VolumeTypeHDD - } else if v.Type == SSDDiskType { - volume.Type = workloads.VolumeTypeSSD - } - - return volume -} - -func zdbReservation(i interface{}, nodeID string) workloads.ZDB { - z := i.(ZDB) - - zdb := workloads.ZDB{ - WorkloadId: 1, - NodeId: nodeID, - // ReservationID: - Size: int64(z.Size), - Password: z.Password, - Public: z.Public, - // StatsAggregator: - // FarmerTid: - } - if z.DiskType == pkg.SSDDevice { - zdb.DiskType = workloads.DiskTypeHDD - } else if z.DiskType == pkg.HDDDevice { - zdb.DiskType = workloads.DiskTypeSSD - } - - if z.Mode == pkg.ZDBModeUser { - zdb.Mode = workloads.ZDBModeUser - } else if z.Mode == pkg.ZDBModeSeq { - zdb.Mode = workloads.ZDBModeSeq - } - - return zdb -} - -func k8sReservation(i interface{}, nodeID string) workloads.K8S { - k := i.(Kubernetes) - - k8s := workloads.K8S{ - WorkloadId: 1, - NodeId: nodeID, - Size: int64(k.Size), - NetworkId: string(k.NetworkID), - Ipaddress: k.IP, - ClusterSecret: k.ClusterSecret, - MasterIps: k.MasterIPs, - SshKeys: k.SSHKeys, - } - - return k8s -} - // ResultState type type ResultState workloads.ResultStateEnum diff --git a/pkg/provision/resource_units.go b/pkg/provision/resource_units.go index 9db88bddf..ec519fc1d 100644 --- a/pkg/provision/resource_units.go +++ b/pkg/provision/resource_units.go @@ -1,124 +1,11 @@ package provision -import ( - "encoding/json" - "fmt" +// ResourceUnits type +type ResourceUnits string + +var ( + ResourceUnitsCRU = ResourceUnits("CRU") + ResourceUnitsMRU = ResourceUnits("MRU") + ResourceUnitsHRU = ResourceUnits("HRU") + ResourceUnitsSRU = ResourceUnits("SRU") ) - -const ( - mib = uint64(1024 * 1024) - gib = uint64(mib * 1024) -) - -func (s *FSStore) processResourceUnits(r *Reservation, addOrRemoveBool bool) error { - - var ( - u resourceUnits - err error - ) - - switch r.Type { - case VolumeReservation: - u, err = processVolume(r) - case ContainerReservation: - u, err = processContainer(r) - case ZDBReservation: - u, err = processZdb(r) - case KubernetesReservation: - u, err = processKubernetes(r) - } - if err != nil { - return err - } - - if addOrRemoveBool { - s.counters.CRU.Increment(u.CRU) - s.counters.MRU.Increment(u.MRU) - s.counters.SRU.Increment(u.SRU) - s.counters.HRU.Increment(u.HRU) - } else { - s.counters.CRU.Decrement(u.CRU) - s.counters.MRU.Decrement(u.MRU) - s.counters.SRU.Decrement(u.SRU) - s.counters.HRU.Decrement(u.HRU) - } - - return nil -} - -type resourceUnits struct { - SRU uint64 `json:"sru,omitempty"` - HRU uint64 `json:"hru,omitempty"` - MRU uint64 `json:"mru,omitempty"` - CRU uint64 `json:"cru,omitempty"` -} - -func processVolume(r *Reservation) (u resourceUnits, err error) { - var volume Volume - if err = json.Unmarshal(r.Data, &volume); err != nil { - return u, err - } - - // volume.size and SRU is in GiB - switch volume.Type { - case SSDDiskType: - u.SRU = volume.Size * gib - case HDDDiskType: - u.HRU = volume.Size * gib - } - - return u, nil -} - -func processContainer(r *Reservation) (u resourceUnits, err error) { - var cont Container - if err = json.Unmarshal(r.Data, &cont); err != nil { - return u, err - } - u.CRU = uint64(cont.Capacity.CPU) - // memory is in MiB - u.MRU = cont.Capacity.Memory * mib - u.SRU = 256 * mib // 250MiB are allocated on SSD for the root filesystem used by the flist - - return u, nil -} - -func processZdb(r *Reservation) (u resourceUnits, err error) { - if r.Type != ZDBReservation { - return u, fmt.Errorf("wrong type or reservation %s, excepted %s", r.Type, ZDBReservation) - } - var zdbVolume ZDB - if err := json.Unmarshal(r.Data, &zdbVolume); err != nil { - return u, err - } - - switch zdbVolume.DiskType { - case "SSD": - u.SRU = zdbVolume.Size * gib - case "HDD": - u.HRU = zdbVolume.Size * gib - } - - return u, nil -} - -func processKubernetes(r *Reservation) (u resourceUnits, err error) { - var k8s Kubernetes - if err = json.Unmarshal(r.Data, &k8s); err != nil { - return u, err - } - - // size are defined at https://github.com/threefoldtech/zos/blob/master/pkg/provision/kubernetes.go#L311 - switch k8s.Size { - case 1: - u.CRU = 1 - u.MRU = 2 * gib - u.SRU = 50 * gib - case 2: - u.CRU = 2 - u.MRU = 4 * gib - u.SRU = 100 * gib - } - - return u, nil -} diff --git a/pkg/provision/resource_units_test.go b/pkg/provision/resource_units_test.go deleted file mode 100644 index 97a35420c..000000000 --- a/pkg/provision/resource_units_test.go +++ /dev/null @@ -1,245 +0,0 @@ -package provision - -import ( - "encoding/json" - "testing" - - "github.com/threefoldtech/zos/pkg" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_processZDB(t *testing.T) { - type args struct { - r *Reservation - } - - tests := []struct { - name string - args args - want resourceUnits - wantErr bool - }{ - { - name: "zdbSSD", - args: args{ - r: &Reservation{ - Type: ZDBReservation, - Data: mustMarshalJSON(t, ZDB{ - Size: 1, - DiskType: pkg.SSDDevice, - }), - }, - }, - want: resourceUnits{ - SRU: 1 * gib, - }, - wantErr: false, - }, - { - name: "zdbHDD", - args: args{ - r: &Reservation{ - Type: ZDBReservation, - Data: mustMarshalJSON(t, ZDB{ - Size: 1, - DiskType: pkg.HDDDevice, - }), - }, - }, - want: resourceUnits{ - HRU: 1 * gib, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := processZdb(tt.args.r) - if tt.wantErr { - assert.Error(t, err) - } else { - assert.Equal(t, tt.want, got) - } - }) - } -} - -func Test_processVolume(t *testing.T) { - type args struct { - r *Reservation - } - - tests := []struct { - name string - args args - wantU resourceUnits - wantErr bool - }{ - { - name: "volumeSSD", - args: args{ - r: &Reservation{ - Type: VolumeReservation, - Data: mustMarshalJSON(t, Volume{ - Size: 1, - Type: SSDDiskType, - }), - }, - }, - wantU: resourceUnits{ - SRU: 1 * gib, - }, - }, - { - name: "volumeHDD", - args: args{ - r: &Reservation{ - Type: VolumeReservation, - Data: mustMarshalJSON(t, Volume{ - Size: 1, - Type: HDDDiskType, - }), - }, - }, - wantU: resourceUnits{ - HRU: 1 * gib, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotU, err := processVolume(tt.args.r) - if tt.wantErr { - assert.Error(t, err) - } else { - assert.Equal(t, tt.wantU, gotU) - } - }) - } -} - -func Test_processContainer(t *testing.T) { - type args struct { - r *Reservation - } - - tests := []struct { - name string - args args - wantU resourceUnits - wantErr bool - }{ - { - name: "container", - args: args{ - r: &Reservation{ - Type: VolumeReservation, - Data: mustMarshalJSON(t, Container{ - Capacity: ContainerCapacity{ - CPU: 2, - Memory: 1024, - }, - }), - }, - }, - wantU: resourceUnits{ - CRU: 2, - MRU: 1 * gib, - SRU: 256 * mib, - }, - }, - { - name: "container", - args: args{ - r: &Reservation{ - Type: VolumeReservation, - Data: mustMarshalJSON(t, Container{ - Capacity: ContainerCapacity{ - CPU: 2, - Memory: 2048, - }, - }), - }, - }, - wantU: resourceUnits{ - CRU: 2, - MRU: 2 * gib, - SRU: 256 * mib, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotU, err := processContainer(tt.args.r) - if tt.wantErr { - assert.Error(t, err) - } else { - assert.Equal(t, tt.wantU, gotU) - } - }) - } -} - -func Test_processKubernetes(t *testing.T) { - type args struct { - r *Reservation - } - - tests := []struct { - name string - args args - wantU resourceUnits - wantErr bool - }{ - { - name: "k8sSize1", - args: args{ - r: &Reservation{ - Type: KubernetesReservation, - Data: mustMarshalJSON(t, Kubernetes{ - Size: 1, - }), - }, - }, - wantU: resourceUnits{ - CRU: 1, - MRU: 2 * gib, - SRU: 50 * gib, - }, - }, - { - name: "k8sSize2", - args: args{ - r: &Reservation{ - Type: KubernetesReservation, - Data: mustMarshalJSON(t, Kubernetes{ - Size: 2, - }), - }, - }, - wantU: resourceUnits{ - CRU: 2, - MRU: 4 * gib, - SRU: 100 * gib, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotU, err := processKubernetes(tt.args.r) - if tt.wantErr { - assert.Error(t, err) - } else { - assert.Equal(t, tt.wantU, gotU) - } - }) - } -} - -func mustMarshalJSON(t *testing.T, v interface{}) []byte { - b, err := json.Marshal(v) - require.NoError(t, err) - return b -} diff --git a/pkg/provision/source.go b/pkg/provision/source.go index c41dc1759..e1f880384 100644 --- a/pkg/provision/source.go +++ b/pkg/provision/source.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net" - "sort" "sync" "time" @@ -15,12 +14,6 @@ import ( "github.com/threefoldtech/zos/pkg" ) -type pollSource struct { - store ReservationPoller - nodeID string - maxSleep time.Duration -} - var ( // ErrPollEOS can be returned by a reservation poll to // notify the caller that it has reached end of stream @@ -29,7 +22,7 @@ var ( ) // ReservationPoller define the interface to implement -// to poll the BCDB for new reservation +// to poll the Explorer for new reservation type ReservationPoller interface { // Poll ask the store to send us reservation for a specific node ID // from is the used as a filter to which reservation to use as @@ -38,59 +31,9 @@ type ReservationPoller interface { Poll(nodeID pkg.Identifier, from uint64) ([]*Reservation, error) } +// ReservationConverterFunc is used to convert from the explorer workloads type into the +// internal Reservation type type ReservationConverterFunc func(w workloads.ReservationWorkload) (*Reservation, error) -type ResultConverterFunc func(result Result) (*workloads.Result, error) - -type reservationPoller struct { - wl client.Workloads - inputConv ReservationConverterFunc - outputConv ResultConverterFunc -} - -// provisionOrder is used to sort the workload type -// in the right order for provisiond -var provisionOrder = map[ReservationType]int{ - DebugReservation: 0, - NetworkReservation: 1, - ZDBReservation: 2, - VolumeReservation: 3, - ContainerReservation: 4, - KubernetesReservation: 5, -} - -func (r *reservationPoller) Poll(nodeID pkg.Identifier, from uint64) ([]*Reservation, error) { - - list, err := r.wl.Workloads(nodeID.Identity(), from) - if err != nil { - return nil, fmt.Errorf("error while retrieving workloads from explorer: %w", err) - } - - result := make([]*Reservation, 0, len(list)) - for _, wl := range list { - log.Info().Msgf("convert %+v", wl) - r, err := r.inputConv(wl) - if err != nil { - return nil, err - } - - result = append(result, r) - } - - // sorts the workloads in the oder they need to be processed by provisiond - sort.Slice(result, func(i int, j int) bool { - return provisionOrder[result[i].Type] < provisionOrder[result[j].Type] - }) - - return result, nil -} - -// ReservationPollerFromWorkloads returns a reservation poller from client.Workloads -func ReservationPollerFromWorkloads(wl client.Workloads, inputConv ReservationConverterFunc) ReservationPoller { - return &reservationPoller{ - wl: wl, - inputConv: inputConv, - } -} // PollSource does a long poll on address to get new and to be deleted // reservations. the server should only return unique reservations @@ -104,6 +47,12 @@ func PollSource(store ReservationPoller, nodeID pkg.Identifier) ReservationSourc } } +type pollSource struct { + store ReservationPoller + nodeID string + maxSleep time.Duration +} + func (s *pollSource) Reservations(ctx context.Context) <-chan *Reservation { ch := make(chan *Reservation) From 6fb1afc01f51a33c3c94257989df4dfe87d4e067 Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Sun, 19 Apr 2020 16:07:46 +0200 Subject: [PATCH 06/14] add documentation to provision package and address some linting warnings --- go.sum | 2 + pkg/app/boot.go | 1 + pkg/provision/crypto_test.go | 102 +-- pkg/provision/doc.go | 10 +- pkg/provision/engine.go | 57 +- pkg/provision/engine_test.go | 132 ++- pkg/provision/explorer/feedback.go | 20 +- pkg/provision/explorer/source.go | 28 +- pkg/provision/interface.go | 5 + pkg/provision/primitives/container_test.go | 838 +++++++++---------- pkg/provision/primitives/converter_test.go | 47 +- pkg/provision/primitives/counters.go | 19 +- pkg/provision/primitives/expiration_test.go | 8 +- pkg/provision/primitives/local_store.go | 3 + pkg/provision/primitives/local_store_test.go | 7 +- pkg/provision/primitives/network_test.go | 154 ++-- pkg/provision/primitives/provisioner.go | 3 + pkg/provision/primitives/volume_test.go | 169 ++-- pkg/provision/primitives/zdb_test.go | 496 ++++++----- pkg/provision/resource_units.go | 2 + pkg/provision/source.go | 5 - 21 files changed, 1040 insertions(+), 1068 deletions(-) diff --git a/go.sum b/go.sum index 9bceb0120..c29855a8a 100644 --- a/go.sum +++ b/go.sum @@ -708,6 +708,7 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 h1:4cFkmztxtMslUX2SctSl+blCyXfpzhGOy9LhKAqSMA4= golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= @@ -759,4 +760,5 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/app/boot.go b/pkg/app/boot.go index 4ee638177..4900deb4c 100644 --- a/pkg/app/boot.go +++ b/pkg/app/boot.go @@ -5,6 +5,7 @@ import ( "path/filepath" ) +// BootedPath is the path where to store the booted flag var BootedPath = "/var/run/modules" // MarkBooted creates a file in a memory diff --git a/pkg/provision/crypto_test.go b/pkg/provision/crypto_test.go index 305b4feb2..2ef66e8f1 100644 --- a/pkg/provision/crypto_test.go +++ b/pkg/provision/crypto_test.go @@ -1,58 +1,48 @@ package provision -import ( - "crypto/rand" - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/threefoldtech/zos/pkg/identity" -) - -func TestVerifySignature(t *testing.T) { - keyPair, err := identity.GenerateKeyPair() - require.NoError(t, err) - - data, err := json.Marshal(Volume{ - Type: SSDDiskType, - Size: 20, - }) - require.NoError(t, err) - - r := &Reservation{ - ID: "reservationID", - NodeID: "node1", - User: keyPair.Identity(), - Type: ContainerReservation, - Data: data, - } - - err = r.Sign(keyPair.PrivateKey) - require.NoError(t, err) - - err = Verify(r) - assert.NoError(t, err) - - validSignature := make([]byte, len(r.Signature)) - copy(validSignature, r.Signature) - - // corrupt the signature - _, err = rand.Read(r.Signature) - require.NoError(t, err) - - err = Verify(r) - assert.Error(t, err) - - // restore signature - copy(r.Signature, validSignature) - - // sanity test - err = Verify(r) - require.NoError(t, err) - - // change the reservation - r.User = "attackerID" - err = Verify(r) - assert.Error(t, err) -} +// func TestVerifySignature(t *testing.T) { +// keyPair, err := identity.GenerateKeyPair() +// require.NoError(t, err) + +// data, err := json.Marshal(Volume{ +// Type: SSDDiskType, +// Size: 20, +// }) +// require.NoError(t, err) + +// r := &Reservation{ +// ID: "reservationID", +// NodeID: "node1", +// User: keyPair.Identity(), +// Type: ContainerReservation, +// Data: data, +// } + +// err = r.Sign(keyPair.PrivateKey) +// require.NoError(t, err) + +// err = Verify(r) +// assert.NoError(t, err) + +// validSignature := make([]byte, len(r.Signature)) +// copy(validSignature, r.Signature) + +// // corrupt the signature +// _, err = rand.Read(r.Signature) +// require.NoError(t, err) + +// err = Verify(r) +// assert.Error(t, err) + +// // restore signature +// copy(r.Signature, validSignature) + +// // sanity test +// err = Verify(r) +// require.NoError(t, err) + +// // change the reservation +// r.User = "attackerID" +// err = Verify(r) +// assert.Error(t, err) +// } diff --git a/pkg/provision/doc.go b/pkg/provision/doc.go index 2bd9ab8df..3b51a21e7 100644 --- a/pkg/provision/doc.go +++ b/pkg/provision/doc.go @@ -1,8 +1,4 @@ -// Package provision is a daemon that pulls -// on reservation source, and then tries to -// apply these reservations locally. -// Note that, provision module doesn't expose -// any interface on zbus. since it should not -// be driven by users, instead all reservation -// should be pushed by the reservation source. +// Package provision exposes the Engine type. +// Engine is a fully configurable type that can be used to +// implement custom provisioning of workloads package provision diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go index 4e993cb2b..8ca0610de 100644 --- a/pkg/provision/engine.go +++ b/pkg/provision/engine.go @@ -13,6 +13,8 @@ import ( "github.com/rs/zerolog/log" ) +// Engine is the core of this package +// The engine is responsible to manage provision and decomission of workloads on the system type Engine struct { nodeID string source ReservationSource @@ -24,15 +26,30 @@ type Engine struct { statser Statser } +// EngineOps are the configuration of the engine type EngineOps struct { - NodeID string - Source ReservationSource - Cache ReservationCache - Feedback Feedbacker - Provisioners map[ReservationType]ProvisionerFunc + // NodeID is the identity of the system running the engine + NodeID string + // Source is responsible to retrieve reservation for a remote source + Source ReservationSource + // Feedback is used to send provision result to the source + // after the reservation is provisionned + Feedback Feedbacker + // Cache is a used to keep track of which reservation are provisionned on the system + // and know when they expired so they can be decommissioned + Cache ReservationCache + // Provisioners is a function map so the engine knows how to provision the different + // workloads supported by the system running the engine + Provisioners map[ReservationType]ProvisionerFunc + // Decomissioners contains the opposite function from Provisioners + // they are used to decomission workloads from the system Decomissioners map[ReservationType]DecomissionerFunc - Signer Signer - Statser Statser + // Signer is used to authenticate the result send to the source + Signer Signer + // Statser is responsible to keep track of how much workloads and resource units + // are reserved on the system running the engine + // After each provision/decomission the engine sends statistics update to the staster + Statser Statser } // New creates a new engine. Once started, the engine @@ -53,10 +70,13 @@ func New(opts EngineOps) *Engine { } } -// Run starts processing reservation resource. Then try to allocate -// reservations +// Run starts reader reservation from the Source and handle them func (e *Engine) Run(ctx context.Context) error { + if err := e.cache.Sync(e.statser); err != nil { + return fmt.Errorf("failed to synchronize statser: %w", err) + } + ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -229,6 +249,7 @@ func (e *Engine) updateStats() error { return e.feedback.UpdateStats(e.nodeID, wl, r) } +// Counters is a zbus stream that sends statistics from the engine func (e *Engine) Counters(ctx context.Context) <-chan pkg.ProvisionCounters { ch := make(chan pkg.ProvisionCounters) go func() { @@ -238,18 +259,18 @@ func (e *Engine) Counters(ctx context.Context) <-chan pkg.ProvisionCounters { case <-ctx.Done(): } - // c := e.cache.Counters() - // pc := pkg.ProvisionCounters{ - // Container: int64(c.containers.Current()), - // Network: int64(c.networks.Current()), - // ZDB: int64(c.zdbs.Current()), - // Volume: int64(c.volumes.Current()), - // VM: int64(c.vms.Current()), - // } + wls := e.statser.CurrentWorkloads() + pc := pkg.ProvisionCounters{ + Container: int64(wls.Container), + Network: int64(wls.Network), + ZDB: int64(wls.ZDBNamespace), + Volume: int64(wls.Volume), + VM: int64(wls.K8sVM), + } select { case <-ctx.Done(): - // case ch <- pc: + case ch <- pc: } } }() diff --git a/pkg/provision/engine_test.go b/pkg/provision/engine_test.go index 14017c603..e9f2765c1 100644 --- a/pkg/provision/engine_test.go +++ b/pkg/provision/engine_test.go @@ -1,82 +1,70 @@ package provision -import ( - "encoding/json" - "io/ioutil" +// func TestEngine(t *testing.T) { +// td, err := ioutil.TempDir("", "") +// require.NoError(t, err) +// defer t.Cleanup(func() { +// os.RemoveAll(td) +// }) - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/threefoldtech/zos/pkg" +// nodeID := "BhPhHVhfU8qerzzh1BGBgcQ7SQxQtND3JwuxSPoRzqkY" +// store := &FSStore{ +// root: td, +// } - "os" - "testing" -) +// engine := &defaultEngine{ +// nodeID: nodeID, +// store: store, +// } -func TestEngine(t *testing.T) { - td, err := ioutil.TempDir("", "") - require.NoError(t, err) - defer t.Cleanup(func() { - os.RemoveAll(td) - }) +// mustJSONMarshal := func(v interface{}) []byte { +// b, err := json.Marshal(v) +// require.NoError(t, err) +// return b +// } - nodeID := "BhPhHVhfU8qerzzh1BGBgcQ7SQxQtND3JwuxSPoRzqkY" - store := &FSStore{ - root: td, - } +// err = engine.store.Add(&Reservation{ +// ID: "1-1", +// Type: VolumeReservation, +// Data: mustJSONMarshal(Volume{ +// Size: 1, +// Type: HDDDiskType, +// }), +// }) +// require.NoError(t, err) - engine := &defaultEngine{ - nodeID: nodeID, - store: store, - } +// err = engine.store.Add(&Reservation{ +// ID: "3-1", +// Type: ZDBReservation, +// Data: mustJSONMarshal(ZDB{ +// Size: 15, +// Mode: pkg.ZDBModeSeq, +// DiskType: pkg.SSDDevice, +// }), +// }) +// require.NoError(t, err) - mustJSONMarshal := func(v interface{}) []byte { - b, err := json.Marshal(v) - require.NoError(t, err) - return b - } +// err = engine.store.Add(&Reservation{ +// ID: "4-1", +// Type: ContainerReservation, +// Data: mustJSONMarshal(Container{ +// Capacity: ContainerCapacity{ +// CPU: 2, +// Memory: 4096, +// }, +// }), +// }) +// require.NoError(t, err) - err = engine.store.Add(&Reservation{ - ID: "1-1", - Type: VolumeReservation, - Data: mustJSONMarshal(Volume{ - Size: 1, - Type: HDDDiskType, - }), - }) - require.NoError(t, err) +// resources, workloads := engine.capacityUsed() +// assert.Equal(t, uint64(2), resources.Cru) +// assert.Equal(t, float64(4), resources.Mru) +// assert.Equal(t, float64(15.25), resources.Sru) +// assert.Equal(t, float64(1), resources.Hru) - err = engine.store.Add(&Reservation{ - ID: "3-1", - Type: ZDBReservation, - Data: mustJSONMarshal(ZDB{ - Size: 15, - Mode: pkg.ZDBModeSeq, - DiskType: pkg.SSDDevice, - }), - }) - require.NoError(t, err) - - err = engine.store.Add(&Reservation{ - ID: "4-1", - Type: ContainerReservation, - Data: mustJSONMarshal(Container{ - Capacity: ContainerCapacity{ - CPU: 2, - Memory: 4096, - }, - }), - }) - require.NoError(t, err) - - resources, workloads := engine.capacityUsed() - assert.Equal(t, uint64(2), resources.Cru) - assert.Equal(t, float64(4), resources.Mru) - assert.Equal(t, float64(15.25), resources.Sru) - assert.Equal(t, float64(1), resources.Hru) - - assert.EqualValues(t, 1, workloads.Container) - assert.EqualValues(t, 0, workloads.Network) - assert.EqualValues(t, 1, workloads.Volume) - assert.EqualValues(t, 1, workloads.ZDBNamespace) - assert.EqualValues(t, 0, workloads.K8sVM) -} +// assert.EqualValues(t, 1, workloads.Container) +// assert.EqualValues(t, 0, workloads.Network) +// assert.EqualValues(t, 1, workloads.Volume) +// assert.EqualValues(t, 1, workloads.ZDBNamespace) +// assert.EqualValues(t, 0, workloads.K8sVM) +// } diff --git a/pkg/provision/explorer/feedback.go b/pkg/provision/explorer/feedback.go index 7aef82121..a93bd796f 100644 --- a/pkg/provision/explorer/feedback.go +++ b/pkg/provision/explorer/feedback.go @@ -8,31 +8,37 @@ import ( "github.com/threefoldtech/zos/pkg/provision" ) -type ExplorerFeedback struct { +// Feedback is an implementation of the provision.Feedbacker +// that u sends results to the TFExplorer: https://github.com/threefoldtech/tfexplorer +type Feedback struct { client *client.Client converter provision.ResultConverterFunc } -func NewExplorerFeedback(client *client.Client, converter provision.ResultConverterFunc) *ExplorerFeedback { - return &ExplorerFeedback{ +// NewFeedback creates an ExplorerFeedback +func NewFeedback(client *client.Client, converter provision.ResultConverterFunc) *Feedback { + return &Feedback{ client: client, converter: converter, } } -func (e *ExplorerFeedback) Feedback(nodeID string, r *provision.Result) error { +// Feedback implements provision.Feedbacker +func (e *Feedback) Feedback(nodeID string, r *provision.Result) error { wr, err := e.converter(*r) if err != nil { - return fmt.Errorf("failed to convert result into schema type: %w") + return fmt.Errorf("failed to convert result into schema type: %w", err) } return e.client.Workloads.WorkloadPutResult(nodeID, r.ID, *wr) } -func (e *ExplorerFeedback) Deleted(nodeID, id string) error { +// Deleted implements provision.Feedbacker +func (e *Feedback) Deleted(nodeID, id string) error { return e.client.Workloads.WorkloadPutDeleted(nodeID, id) } -func (e *ExplorerFeedback) UpdateStats(nodeID string, w directory.WorkloadAmount, u directory.ResourceAmount) error { +// UpdateStats implements provision.Feedbacker +func (e *Feedback) UpdateStats(nodeID string, w directory.WorkloadAmount, u directory.ResourceAmount) error { return e.client.Directory.NodeUpdateUsedResources(nodeID, u, w) } diff --git a/pkg/provision/explorer/source.go b/pkg/provision/explorer/source.go index 0687b92ac..fcb5855f5 100644 --- a/pkg/provision/explorer/source.go +++ b/pkg/provision/explorer/source.go @@ -10,13 +10,28 @@ import ( "github.com/threefoldtech/zos/pkg/provision" ) -type ReservationPoller struct { +// Poller is an implementation of the provision.ReservationPoller +// that retrieve reservation from the TFExplorer: https://github.com/threefoldtech/tfexplorer +type Poller struct { wl client.Workloads inputConv provision.ReservationConverterFunc provisionOrder map[provision.ReservationType]int } -func (r *ReservationPoller) Poll(nodeID pkg.Identifier, from uint64) ([]*provision.Reservation, error) { +// NewPoller returns a reservation poller +// inputConv is a function used by the provision.Engine to convert date received from the explorer to into the internal type of the system +// provisionOrder is a map with each primitive type as key. It is used to order the reservation before sending them to the engine. +// This can be useful if some workloads in a same reservation depends on each other +func NewPoller(cl *client.Client, inputConv provision.ReservationConverterFunc, provisionOrder map[provision.ReservationType]int) *Poller { + return &Poller{ + wl: cl.Workloads, + inputConv: inputConv, + provisionOrder: provisionOrder, + } +} + +// Poll implements provision.ReservationPoller +func (r *Poller) Poll(nodeID pkg.Identifier, from uint64) ([]*provision.Reservation, error) { list, err := r.wl.Workloads(nodeID.Identity(), from) if err != nil { @@ -43,12 +58,3 @@ func (r *ReservationPoller) Poll(nodeID pkg.Identifier, from uint64) ([]*provisi return result, nil } - -// ReservationPollerFromWorkloads returns a reservation poller from client.Workloads -func ReservationPollerFromWorkloads(wl client.Workloads, inputConv provision.ReservationConverterFunc, provisionOrder map[provision.ReservationType]int) *ReservationPoller { - return &ReservationPoller{ - wl: wl, - inputConv: inputConv, - provisionOrder: provisionOrder, - } -} diff --git a/pkg/provision/interface.go b/pkg/provision/interface.go index 1f6d50024..55327f6ad 100644 --- a/pkg/provision/interface.go +++ b/pkg/provision/interface.go @@ -21,6 +21,10 @@ type ProvisionerFunc func(ctx context.Context, reservation *Reservation) (interf // DecomissionerFunc is the function called by the Engine to decomission a workload type DecomissionerFunc func(ctx context.Context, reservation *Reservation) error +// ReservationConverterFunc is used to convert from the explorer workloads type into the +// internal Reservation type +type ReservationConverterFunc func(w workloads.ReservationWorkload) (*Reservation, error) + //ResultConverterFunc is used to convert internal Result type to the explorer workload result type ResultConverterFunc func(result Result) (*workloads.Result, error) @@ -31,6 +35,7 @@ type ReservationCache interface { Get(id string) (*Reservation, error) Remove(id string) error Exists(id string) (bool, error) + Sync(Statser) error } // Feedbacker defines the method that needs to be implemented diff --git a/pkg/provision/primitives/container_test.go b/pkg/provision/primitives/container_test.go index 796feb163..0b5ea9dfe 100644 --- a/pkg/provision/primitives/container_test.go +++ b/pkg/provision/primitives/container_test.go @@ -1,427 +1,415 @@ package primitives -import ( - "context" - "fmt" - "net" - "testing" - - "github.com/stretchr/testify/require" - "github.com/threefoldtech/zbus" - "github.com/threefoldtech/zos/pkg" -) - -func TestContainerProvisionExists(t *testing.T) { - require := require.New(t) - - var client TestClient - var cache TestOwnerCache - - ctx := context.Background() - ctx = WithZBus(ctx, &client) - ctx = WithOwnerCache(ctx, &cache) - - // const module = "network" - // version := zbus.ObjectID{Name: "network", Version: "0.0.1"} - - container := Container{ - FList: "https://hub.grid.tf/thabet/redis.flist", - Network: Network{ - NetworkID: pkg.NetID("net1"), - IPs: []net.IP{ - net.ParseIP("192.168.1.1"), - }, - }, - } - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: ContainerReservation, - Data: MustMarshal(t, container), - } - - // first, the provision will inspect the container - client.On("Request", - "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Inspect", - fmt.Sprintf("ns%s", reservation.User), - pkg.ContainerID(reservation.ID)). - Return(pkg.Container{Name: reservation.ID}, nil) - - result, err := containerProvisionImpl(ctx, &reservation) - require.NoError(err) - require.Equal("reservation-id", result.ID) -} - -func TestContainerProvisionNew(t *testing.T) { - require := require.New(t) - - var client TestClient - var cache TestOwnerCache - - ctx := context.Background() - ctx = WithZBus(ctx, &client) - ctx = WithOwnerCache(ctx, &cache) - - // const module = "network" - // version := zbus.ObjectID{Name: "network", Version: "0.0.1"} - - container := Container{ - FList: "https://hub.grid.tf/thabet/redis.flist", - Network: Network{ - NetworkID: pkg.NetID("net1"), - IPs: []net.IP{ - net.ParseIP("192.168.1.1"), - }, - PublicIP6: false, - }, - } - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: ContainerReservation, - Data: MustMarshal(t, container), - } - - userNs := fmt.Sprintf("ns%s", reservation.User) - // first, the provision will inspect the container - // see if it exists - client.On("Request", - "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Inspect", - userNs, - pkg.ContainerID(reservation.ID)). - Return(pkg.Container{}, zbus.RemoteError{"does not exist"}) - - netID := networkID(reservation.User, string(container.Network.NetworkID)) - - // since it's a new container, a call to join the network is made - client.On("Request", - "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, - "Join", - netID, reservation.ID, []string{"192.168.1.1"}, false). - Return(pkg.Member{ - Namespace: "net-ns", - IPv4: net.ParseIP("192.168.1.1"), - }, nil) - - // then another call to mount the flist - client.On("Request", - "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, - "Mount", - container.FList, container.FlistStorage, pkg.DefaultMountOptions). - Return("/tmp/root", nil) - - // once root is mounted, a final call to contd to run the container - client.On("Request", - "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Run", - userNs, pkg.Container{ - Name: reservation.ID, - RootFS: "/tmp/root", //returned by flist.Mount - Network: pkg.NetworkInfo{ - Namespace: "net-ns", - }, - }). - Return(pkg.ContainerID(reservation.ID), nil) - - result, err := containerProvisionImpl(ctx, &reservation) - require.NoError(err) - require.Equal("reservation-id", result.ID) - require.Equal("192.168.1.1", result.IPv4) -} - -func TestContainerProvisionWithMounts(t *testing.T) { - require := require.New(t) - - var client TestClient - var cache TestOwnerCache - - ctx := context.Background() - ctx = WithZBus(ctx, &client) - ctx = WithOwnerCache(ctx, &cache) - - container := Container{ - FList: "https://hub.grid.tf/thabet/redis.flist", - Network: Network{ - NetworkID: pkg.NetID("net1"), - IPs: []net.IP{ - net.ParseIP("192.168.1.1"), - }, - PublicIP6: false, - }, - Mounts: []Mount{ - { - VolumeID: "vol1", - Mountpoint: "/opt", - }, - }, - } - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: ContainerReservation, - Data: MustMarshal(t, container), - } - - //NOTE: since we have moutns, a call is made to get the owner - //of the volume to validate it's the same owner of the reservation - cache.On("OwnerOf", "vol1").Return(reservation.User, nil) - - userNs := fmt.Sprintf("ns%s", reservation.User) - // first, the provision will inspect the container - // see if it exists - client.On("Request", - "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Inspect", - userNs, - pkg.ContainerID(reservation.ID)). - Return(pkg.Container{}, zbus.RemoteError{"does not exist"}) - - netID := networkID(reservation.User, string(container.Network.NetworkID)) - - // since it's a new container, a call to join the network is made - client.On("Request", - "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, - "Join", - netID, reservation.ID, []string{"192.168.1.1"}, false). - Return(pkg.Member{ - Namespace: "net-ns", - IPv4: net.ParseIP("192.168.1.1"), - }, nil) - - // then another call to mount the flist - client.On("Request", - "storage", zbus.ObjectID{Name: "storage", Version: "0.0.1"}, - "Path", - "vol1"). - Return("/some/path/to/vol1", nil) - - //for each volume in the list, a call is made to get the mount path - //of that volume - client.On("Request", - "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, - "Mount", - container.FList, container.FlistStorage, pkg.DefaultMountOptions). - Return("/tmp/root", nil) - - // once root is mounted, a final call to contd to run the container - client.On("Request", - "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Run", - userNs, pkg.Container{ - Name: reservation.ID, - RootFS: "/tmp/root", //returned by flist.Mount - Network: pkg.NetworkInfo{ - Namespace: "net-ns", - }, - Mounts: []pkg.MountInfo{ - { - Source: "/some/path/to/vol1", - Target: "/opt", - }, - }, - }). - Return(pkg.ContainerID(reservation.ID), nil) - - result, err := containerProvisionImpl(ctx, &reservation) - require.NoError(err) - require.Equal("reservation-id", result.ID) - require.Equal("192.168.1.1", result.IPv4) -} - -func TestContainerDecomissionExists(t *testing.T) { - require := require.New(t) - - var client TestClient - var cache TestOwnerCache - - ctx := context.Background() - ctx = WithZBus(ctx, &client) - ctx = WithOwnerCache(ctx, &cache) - - // const module = "network" - // version := zbus.ObjectID{Name: "network", Version: "0.0.1"} - - container := Container{ - FList: "https://hub.grid.tf/thabet/redis.flist", - Network: Network{ - NetworkID: pkg.NetID("net1"), - IPs: []net.IP{ - net.ParseIP("192.168.1.1"), - }, - }, - } - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: ContainerReservation, - Data: MustMarshal(t, container), - } - rootFS := "/mnt/flist_root" - - // first, the decomission will inspect the container - client.On("Request", - "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Inspect", - fmt.Sprintf("ns%s", reservation.User), - pkg.ContainerID(reservation.ID)). - Return(pkg.Container{Name: reservation.ID, RootFS: rootFS}, nil) - - // second, the decomission will delete the container - client.On("Request", - "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Delete", - fmt.Sprintf("ns%s", reservation.User), - pkg.ContainerID(reservation.ID)). - Return(nil) - - // third, unmount the flist of the container - client.On("Request", - "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, - "Umount", - rootFS). - Return(nil) - - netID := networkID(reservation.User, string(container.Network.NetworkID)) - - // fourth, check if the network still exists - client.On("Request", - "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, - "GetSubnet", - netID). - Return(net.IPNet{}, nil) // return a nil error, the network still exists - - // fifths, leave the container network namespace - client.On("Request", - "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, - "Leave", - netID, - reservation.ID). - Return(nil) - - err := containerDecommission(ctx, &reservation) - require.NoError(err) -} - -func TestContainerDecomissionContainerGone(t *testing.T) { - require := require.New(t) - - var client TestClient - var cache TestOwnerCache - - ctx := context.Background() - ctx = WithZBus(ctx, &client) - ctx = WithOwnerCache(ctx, &cache) - - container := Container{ - FList: "https://hub.grid.tf/thabet/redis.flist", - Network: Network{ - NetworkID: pkg.NetID("net1"), - IPs: []net.IP{ - net.ParseIP("192.168.1.1"), - }, - }, - } - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: ContainerReservation, - Data: MustMarshal(t, container), - } - - // first, the decomission will inspect the container - client.On("Request", - "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Inspect", - fmt.Sprintf("ns%s", reservation.User), - pkg.ContainerID(reservation.ID)). - Return(nil, fmt.Errorf("container not found")) - - // second, check if the network still exists - netID := networkID(reservation.User, string(container.Network.NetworkID)) - client.On("Request", - "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, - "GetSubnet", - netID). - Return(net.IPNet{}, nil) // return a nil error, the network still exists - - // third, leave the container network namespace - client.On("Request", - "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, - "Leave", - netID, - reservation.ID). - Return(nil) - - err := containerDecommission(ctx, &reservation) - require.NoError(err) -} - -func TestContainerDecomissionNetworkGone(t *testing.T) { - require := require.New(t) - - var client TestClient - var cache TestOwnerCache - - ctx := context.Background() - ctx = WithZBus(ctx, &client) - ctx = WithOwnerCache(ctx, &cache) - - container := Container{ - FList: "https://hub.grid.tf/thabet/redis.flist", - Network: Network{ - NetworkID: pkg.NetID("net1"), - IPs: []net.IP{ - net.ParseIP("192.168.1.1"), - }, - }, - } - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: ContainerReservation, - Data: MustMarshal(t, container), - } - rootFS := "/mnt/flist_root" - - // first, the decomission will inspect the container - client.On("Request", - "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Inspect", - fmt.Sprintf("ns%s", reservation.User), - pkg.ContainerID(reservation.ID)). - Return(pkg.Container{Name: reservation.ID, RootFS: rootFS}, nil) - - // second, the decomission will delete the container - client.On("Request", - "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Delete", - fmt.Sprintf("ns%s", reservation.User), - pkg.ContainerID(reservation.ID)). - Return(nil) - - // third, unmount the flist of the container - client.On("Request", - "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, - "Umount", - rootFS). - Return(nil) - - netID := networkID(reservation.User, string(container.Network.NetworkID)) - // fourth, check if the network still exists - client.On("Request", - "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, - "GetSubnet", - netID). - Return(net.IPNet{}, fmt.Errorf("network not found")) - - err := containerDecommission(ctx, &reservation) - require.NoError(err) -} +// func TestContainerProvisionExists(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// var cache TestOwnerCache + +// p := NewProvisioner(cache, client) +// ctx := context.Background() + +// // const module = "network" +// // version := zbus.ObjectID{Name: "network", Version: "0.0.1"} + +// container := Container{ +// FList: "https://hub.grid.tf/thabet/redis.flist", +// Network: Network{ +// NetworkID: pkg.NetID("net1"), +// IPs: []net.IP{ +// net.ParseIP("192.168.1.1"), +// }, +// }, +// } + +// reservation := provision.Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: ContainerReservation, +// Data: MustMarshal(t, container), +// } + +// // first, the provision will inspect the container +// client.On("Request", +// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Inspect", +// fmt.Sprintf("ns%s", reservation.User), +// pkg.ContainerID(reservation.ID)). +// Return(pkg.Container{Name: reservation.ID}, nil) + +// result, err := p.containerProvisionImpl(ctx, &reservation) +// require.NoError(err) +// require.Equal("reservation-id", result.ID) +// } + +// func TestContainerProvisionNew(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// var cache TestOwnerCache + +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) +// ctx = WithOwnerCache(ctx, &cache) + +// // const module = "network" +// // version := zbus.ObjectID{Name: "network", Version: "0.0.1"} + +// container := Container{ +// FList: "https://hub.grid.tf/thabet/redis.flist", +// Network: Network{ +// NetworkID: pkg.NetID("net1"), +// IPs: []net.IP{ +// net.ParseIP("192.168.1.1"), +// }, +// PublicIP6: false, +// }, +// } + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: ContainerReservation, +// Data: MustMarshal(t, container), +// } + +// userNs := fmt.Sprintf("ns%s", reservation.User) +// // first, the provision will inspect the container +// // see if it exists +// client.On("Request", +// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Inspect", +// userNs, +// pkg.ContainerID(reservation.ID)). +// Return(pkg.Container{}, zbus.RemoteError{"does not exist"}) + +// netID := networkID(reservation.User, string(container.Network.NetworkID)) + +// // since it's a new container, a call to join the network is made +// client.On("Request", +// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, +// "Join", +// netID, reservation.ID, []string{"192.168.1.1"}, false). +// Return(pkg.Member{ +// Namespace: "net-ns", +// IPv4: net.ParseIP("192.168.1.1"), +// }, nil) + +// // then another call to mount the flist +// client.On("Request", +// "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, +// "Mount", +// container.FList, container.FlistStorage, pkg.DefaultMountOptions). +// Return("/tmp/root", nil) + +// // once root is mounted, a final call to contd to run the container +// client.On("Request", +// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Run", +// userNs, pkg.Container{ +// Name: reservation.ID, +// RootFS: "/tmp/root", //returned by flist.Mount +// Network: pkg.NetworkInfo{ +// Namespace: "net-ns", +// }, +// }). +// Return(pkg.ContainerID(reservation.ID), nil) + +// result, err := containerProvisionImpl(ctx, &reservation) +// require.NoError(err) +// require.Equal("reservation-id", result.ID) +// require.Equal("192.168.1.1", result.IPv4) +// } + +// func TestContainerProvisionWithMounts(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// var cache TestOwnerCache + +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) +// ctx = WithOwnerCache(ctx, &cache) + +// container := Container{ +// FList: "https://hub.grid.tf/thabet/redis.flist", +// Network: Network{ +// NetworkID: pkg.NetID("net1"), +// IPs: []net.IP{ +// net.ParseIP("192.168.1.1"), +// }, +// PublicIP6: false, +// }, +// Mounts: []Mount{ +// { +// VolumeID: "vol1", +// Mountpoint: "/opt", +// }, +// }, +// } + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: ContainerReservation, +// Data: MustMarshal(t, container), +// } + +// //NOTE: since we have moutns, a call is made to get the owner +// //of the volume to validate it's the same owner of the reservation +// cache.On("OwnerOf", "vol1").Return(reservation.User, nil) + +// userNs := fmt.Sprintf("ns%s", reservation.User) +// // first, the provision will inspect the container +// // see if it exists +// client.On("Request", +// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Inspect", +// userNs, +// pkg.ContainerID(reservation.ID)). +// Return(pkg.Container{}, zbus.RemoteError{"does not exist"}) + +// netID := networkID(reservation.User, string(container.Network.NetworkID)) + +// // since it's a new container, a call to join the network is made +// client.On("Request", +// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, +// "Join", +// netID, reservation.ID, []string{"192.168.1.1"}, false). +// Return(pkg.Member{ +// Namespace: "net-ns", +// IPv4: net.ParseIP("192.168.1.1"), +// }, nil) + +// // then another call to mount the flist +// client.On("Request", +// "storage", zbus.ObjectID{Name: "storage", Version: "0.0.1"}, +// "Path", +// "vol1"). +// Return("/some/path/to/vol1", nil) + +// //for each volume in the list, a call is made to get the mount path +// //of that volume +// client.On("Request", +// "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, +// "Mount", +// container.FList, container.FlistStorage, pkg.DefaultMountOptions). +// Return("/tmp/root", nil) + +// // once root is mounted, a final call to contd to run the container +// client.On("Request", +// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Run", +// userNs, pkg.Container{ +// Name: reservation.ID, +// RootFS: "/tmp/root", //returned by flist.Mount +// Network: pkg.NetworkInfo{ +// Namespace: "net-ns", +// }, +// Mounts: []pkg.MountInfo{ +// { +// Source: "/some/path/to/vol1", +// Target: "/opt", +// }, +// }, +// }). +// Return(pkg.ContainerID(reservation.ID), nil) + +// result, err := containerProvisionImpl(ctx, &reservation) +// require.NoError(err) +// require.Equal("reservation-id", result.ID) +// require.Equal("192.168.1.1", result.IPv4) +// } + +// func TestContainerDecomissionExists(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// var cache TestOwnerCache + +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) +// ctx = WithOwnerCache(ctx, &cache) + +// // const module = "network" +// // version := zbus.ObjectID{Name: "network", Version: "0.0.1"} + +// container := Container{ +// FList: "https://hub.grid.tf/thabet/redis.flist", +// Network: Network{ +// NetworkID: pkg.NetID("net1"), +// IPs: []net.IP{ +// net.ParseIP("192.168.1.1"), +// }, +// }, +// } + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: ContainerReservation, +// Data: MustMarshal(t, container), +// } +// rootFS := "/mnt/flist_root" + +// // first, the decomission will inspect the container +// client.On("Request", +// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Inspect", +// fmt.Sprintf("ns%s", reservation.User), +// pkg.ContainerID(reservation.ID)). +// Return(pkg.Container{Name: reservation.ID, RootFS: rootFS}, nil) + +// // second, the decomission will delete the container +// client.On("Request", +// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Delete", +// fmt.Sprintf("ns%s", reservation.User), +// pkg.ContainerID(reservation.ID)). +// Return(nil) + +// // third, unmount the flist of the container +// client.On("Request", +// "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, +// "Umount", +// rootFS). +// Return(nil) + +// netID := networkID(reservation.User, string(container.Network.NetworkID)) + +// // fourth, check if the network still exists +// client.On("Request", +// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, +// "GetSubnet", +// netID). +// Return(net.IPNet{}, nil) // return a nil error, the network still exists + +// // fifths, leave the container network namespace +// client.On("Request", +// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, +// "Leave", +// netID, +// reservation.ID). +// Return(nil) + +// err := containerDecommission(ctx, &reservation) +// require.NoError(err) +// } + +// func TestContainerDecomissionContainerGone(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// var cache TestOwnerCache + +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) +// ctx = WithOwnerCache(ctx, &cache) + +// container := Container{ +// FList: "https://hub.grid.tf/thabet/redis.flist", +// Network: Network{ +// NetworkID: pkg.NetID("net1"), +// IPs: []net.IP{ +// net.ParseIP("192.168.1.1"), +// }, +// }, +// } + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: ContainerReservation, +// Data: MustMarshal(t, container), +// } + +// // first, the decomission will inspect the container +// client.On("Request", +// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Inspect", +// fmt.Sprintf("ns%s", reservation.User), +// pkg.ContainerID(reservation.ID)). +// Return(nil, fmt.Errorf("container not found")) + +// // second, check if the network still exists +// netID := networkID(reservation.User, string(container.Network.NetworkID)) +// client.On("Request", +// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, +// "GetSubnet", +// netID). +// Return(net.IPNet{}, nil) // return a nil error, the network still exists + +// // third, leave the container network namespace +// client.On("Request", +// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, +// "Leave", +// netID, +// reservation.ID). +// Return(nil) + +// err := containerDecommission(ctx, &reservation) +// require.NoError(err) +// } + +// func TestContainerDecomissionNetworkGone(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// var cache TestOwnerCache + +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) +// ctx = WithOwnerCache(ctx, &cache) + +// container := Container{ +// FList: "https://hub.grid.tf/thabet/redis.flist", +// Network: Network{ +// NetworkID: pkg.NetID("net1"), +// IPs: []net.IP{ +// net.ParseIP("192.168.1.1"), +// }, +// }, +// } + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: ContainerReservation, +// Data: MustMarshal(t, container), +// } +// rootFS := "/mnt/flist_root" + +// // first, the decomission will inspect the container +// client.On("Request", +// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Inspect", +// fmt.Sprintf("ns%s", reservation.User), +// pkg.ContainerID(reservation.ID)). +// Return(pkg.Container{Name: reservation.ID, RootFS: rootFS}, nil) + +// // second, the decomission will delete the container +// client.On("Request", +// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Delete", +// fmt.Sprintf("ns%s", reservation.User), +// pkg.ContainerID(reservation.ID)). +// Return(nil) + +// // third, unmount the flist of the container +// client.On("Request", +// "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, +// "Umount", +// rootFS). +// Return(nil) + +// netID := networkID(reservation.User, string(container.Network.NetworkID)) +// // fourth, check if the network still exists +// client.On("Request", +// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, +// "GetSubnet", +// netID). +// Return(net.IPNet{}, fmt.Errorf("network not found")) + +// err := containerDecommission(ctx, &reservation) +// require.NoError(err) +// } diff --git a/pkg/provision/primitives/converter_test.go b/pkg/provision/primitives/converter_test.go index 7f1244266..73327a23d 100644 --- a/pkg/provision/primitives/converter_test.go +++ b/pkg/provision/primitives/converter_test.go @@ -1,4 +1,4 @@ -package primitives_test +package primitives import ( "encoding/json" @@ -14,7 +14,6 @@ import ( schema "github.com/threefoldtech/tfexplorer/schema" "github.com/threefoldtech/zos/pkg/container/logger" "github.com/threefoldtech/zos/pkg/container/stats" - "github.com/threefoldtech/zos/pkg/provision" "gotest.tools/assert" ) @@ -49,7 +48,7 @@ func TestTfgridReservationContainer1_ToProvisionType(t *testing.T) { tests := []struct { name string fields fields - want provision.Container + want Container wantErr bool }{ { @@ -66,15 +65,15 @@ func TestTfgridReservationContainer1_ToProvisionType(t *testing.T) { NetworkConnection: nil, StatsAggregator: nil, }, - want: provision.Container{ + want: Container{ FList: "https://hub.grid.tf/tf-official-apps/ubuntu-bionic-build.flist", FlistStorage: "zdb://hub.grid.tf:9900", Env: map[string]string{"FOO": "BAR"}, SecretEnv: nil, Entrypoint: "/sbin/my_init", Interactive: false, - Mounts: []provision.Mount{}, - Network: provision.Network{}, + Mounts: []Mount{}, + Network: Network{}, Logs: []logger.Logs{}, StatsAggregator: []stats.Aggregator{}, }, @@ -107,14 +106,14 @@ func TestTfgridReservationContainer1_ToProvisionType(t *testing.T) { }, }, }, - want: provision.Container{ + want: Container{ FList: "https://hub.grid.tf/tf-official-apps/ubuntu-bionic-build.flist", FlistStorage: "zdb://hub.grid.tf:9900", Env: map[string]string{"FOO": "BAR"}, SecretEnv: nil, Entrypoint: "/sbin/my_init", Interactive: false, - Mounts: []provision.Mount{ + Mounts: []Mount{ { VolumeID: "volume1", Mountpoint: "/mnt", @@ -124,7 +123,7 @@ func TestTfgridReservationContainer1_ToProvisionType(t *testing.T) { Mountpoint: "/data", }, }, - Network: provision.Network{ + Network: Network{ NetworkID: "net1", IPs: []net.IP{net.ParseIP("10.0.0.1")}, }, @@ -149,7 +148,7 @@ func TestTfgridReservationContainer1_ToProvisionType(t *testing.T) { NetworkConnection: tt.fields.NetworkConnection, StatsAggregator: tt.fields.StatsAggregator, } - got, _, err := provision.ContainerToProvisionType(c) + got, _, err := ContainerToProvisionType(c) if !tt.wantErr { require.NoError(t, err) assert.DeepEqual(t, tt.want, got) @@ -172,7 +171,7 @@ func TestTfgridReservationVolume1_ToProvisionType(t *testing.T) { tests := []struct { name string fields fields - want provision.Volume + want Volume wantErr bool }{ { @@ -184,9 +183,9 @@ func TestTfgridReservationVolume1_ToProvisionType(t *testing.T) { Type: workloads.VolumeTypeHDD, StatsAggregator: nil, }, - want: provision.Volume{ + want: Volume{ Size: 10, - Type: provision.HDDDiskType, + Type: HDDDiskType, }, }, { @@ -198,9 +197,9 @@ func TestTfgridReservationVolume1_ToProvisionType(t *testing.T) { Type: workloads.VolumeTypeSSD, StatsAggregator: nil, }, - want: provision.Volume{ + want: Volume{ Size: 10, - Type: provision.SSDDiskType, + Type: SSDDiskType, }, }, } @@ -213,7 +212,7 @@ func TestTfgridReservationVolume1_ToProvisionType(t *testing.T) { Type: tt.fields.Type, StatsAggregator: tt.fields.StatsAggregator, } - got, _, err := provision.VolumeToProvisionType(v) + got, _, err := VolumeToProvisionType(v) if !tt.wantErr { require.NoError(t, err) assert.DeepEqual(t, tt.want, got) @@ -238,7 +237,7 @@ func TestTfgridReservationZdb1_ToProvisionType(t *testing.T) { tests := []struct { name string fields fields - want provision.ZDB + want ZDB wantErr bool }{ { @@ -253,7 +252,7 @@ func TestTfgridReservationZdb1_ToProvisionType(t *testing.T) { DiskType: workloads.DiskTypeHDD, Public: true, }, - want: provision.ZDB{ + want: ZDB{ Size: 10, Mode: pkg.ZDBModeSeq, Password: "supersecret", @@ -274,7 +273,7 @@ func TestTfgridReservationZdb1_ToProvisionType(t *testing.T) { DiskType: workloads.DiskTypeHDD, Public: true, }, - want: provision.ZDB{ + want: ZDB{ Size: 10, Mode: pkg.ZDBModeUser, Password: "supersecret", @@ -295,7 +294,7 @@ func TestTfgridReservationZdb1_ToProvisionType(t *testing.T) { DiskType: workloads.DiskTypeSSD, Public: true, }, - want: provision.ZDB{ + want: ZDB{ Size: 10, Mode: pkg.ZDBModeUser, Password: "supersecret", @@ -317,7 +316,7 @@ func TestTfgridReservationZdb1_ToProvisionType(t *testing.T) { DiskType: tt.fields.DiskType, Public: tt.fields.Public, } - got, _, err := provision.ZDBToProvisionType(z) + got, _, err := ZDBToProvisionType(z) if !tt.wantErr { require.NoError(t, err) assert.DeepEqual(t, tt.want, got) @@ -367,7 +366,7 @@ func TestTfgridReservationNetwork1_ToProvisionType(t *testing.T) { StatsAggregator: tt.fields.StatsAggregator, NetworkResources: tt.fields.NetworkResources, } - got, err := provision.NetworkToProvisionType(n) + got, err := NetworkToProvisionType(n) if !tt.wantErr { require.NoError(t, err) assert.DeepEqual(t, tt.want, got) @@ -422,7 +421,7 @@ func TestTfgridNetworkNetResource1_ToProvisionType(t *testing.T) { WireguardListenPort: tt.fields.WireguardListenPort, Peers: tt.fields.Peers, } - got, err := provision.NetResourceToProvisionType(r) + got, err := NetResourceToProvisionType(r) if !tt.wantErr { require.NoError(t, err) assert.DeepEqual(t, tt.want, got) @@ -491,7 +490,7 @@ func TestWireguardPeer1_ToProvisionType(t *testing.T) { Endpoint: tt.fields.Endpoint, AllowedIprange: tt.fields.AllowedIPs, } - got, err := provision.WireguardToProvisionType(p) + got, err := WireguardToProvisionType(p) if !tt.wantErr { require.NoError(t, err) assert.DeepEqual(t, tt.want, got) diff --git a/pkg/provision/primitives/counters.go b/pkg/provision/primitives/counters.go index 9675072f8..3fbc6c921 100644 --- a/pkg/provision/primitives/counters.go +++ b/pkg/provision/primitives/counters.go @@ -19,20 +19,6 @@ type Counter interface { Current() uint64 } -type counterNop struct{} - -func (c *counterNop) Increment(v uint64) uint64 { - return 0 -} - -func (c *counterNop) Decrement(v uint64) uint64 { - return 0 -} - -func (c *counterNop) Current() uint64 { - return 0 -} - // counterImpl value for safe increment/decrement type counterImpl uint64 @@ -59,7 +45,6 @@ type Counters struct { networks counterImpl zdbs counterImpl vms counterImpl - debugs counterImpl SRU counterImpl // SSD storage in bytes HRU counterImpl // HDD storage in bytes @@ -67,6 +52,7 @@ type Counters struct { CRU counterImpl // CPU count absolute } +// CurrentWorkloads return the number of each workloads provisioned on the system func (c *Counters) CurrentWorkloads() directory.WorkloadAmount { return directory.WorkloadAmount{ Network: uint16(c.containers.Current()), @@ -77,6 +63,7 @@ func (c *Counters) CurrentWorkloads() directory.WorkloadAmount { } } +// CurrentUnits return the number of each resource units reserved on the system func (c *Counters) CurrentUnits() directory.ResourceAmount { return directory.ResourceAmount{ Cru: c.CRU.Current(), @@ -91,6 +78,7 @@ const ( gib = uint64(mib * 1024) ) +// Increment is called by the provision.Engine when a reservation has been provisionned func (c *Counters) Increment(r *provision.Reservation) error { var ( @@ -121,6 +109,7 @@ func (c *Counters) Increment(r *provision.Reservation) error { return nil } +// Decrement is called by the provision.Engine when a reservation has been decommissioned func (c *Counters) Decrement(r *provision.Reservation) error { var ( diff --git a/pkg/provision/primitives/expiration_test.go b/pkg/provision/primitives/expiration_test.go index 033004a28..d62f94d48 100644 --- a/pkg/provision/primitives/expiration_test.go +++ b/pkg/provision/primitives/expiration_test.go @@ -3,11 +3,13 @@ package primitives import ( "testing" "time" + + "github.com/threefoldtech/zos/pkg/provision" ) func TestExpired(t *testing.T) { type args struct { - r *Reservation + r *provision.Reservation } tests := []struct { name string @@ -16,7 +18,7 @@ func TestExpired(t *testing.T) { }{ { name: "expired", - args: args{&Reservation{ + args: args{&provision.Reservation{ Created: time.Now().Add(-time.Minute), Duration: time.Second, }}, @@ -24,7 +26,7 @@ func TestExpired(t *testing.T) { }, { name: "not expired", - args: args{&Reservation{ + args: args{&provision.Reservation{ Created: time.Now(), Duration: time.Minute, }}, diff --git a/pkg/provision/primitives/local_store.go b/pkg/provision/primitives/local_store.go index ff62059f6..615799302 100644 --- a/pkg/provision/primitives/local_store.go +++ b/pkg/provision/primitives/local_store.go @@ -96,7 +96,10 @@ func (s *FSStore) removeAllButPersistent(rootPath string) error { return nil } +// Sync update the statser with all the reservation present in the cache func (s *FSStore) Sync(statser provision.Statser) error { + //this should probably be reversed and moved to the Statser object instead + s.RLock() defer s.RUnlock() diff --git a/pkg/provision/primitives/local_store_test.go b/pkg/provision/primitives/local_store_test.go index 915ca512f..35e72ac0a 100644 --- a/pkg/provision/primitives/local_store_test.go +++ b/pkg/provision/primitives/local_store_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/threefoldtech/zos/pkg/provision" ) func TestLocalStore(t *testing.T) { @@ -19,7 +20,7 @@ func TestLocalStore(t *testing.T) { root string } type args struct { - r *Reservation + r *provision.Reservation } tests := []struct { name string @@ -33,11 +34,11 @@ func TestLocalStore(t *testing.T) { root: root, }, args: args{ - r: &Reservation{ + r: &provision.Reservation{ ID: "r-1", Created: time.Now().UTC().Add(-time.Minute).Round(time.Second), Duration: time.Second * 10, - Tag: Tag{"source": "FSStore"}, + // Tag: Tag{"source": "FSStore"}, }, }, }, diff --git a/pkg/provision/primitives/network_test.go b/pkg/provision/primitives/network_test.go index 7dc049977..5cebf7253 100644 --- a/pkg/provision/primitives/network_test.go +++ b/pkg/provision/primitives/network_test.go @@ -1,91 +1,87 @@ package primitives import ( - "context" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/threefoldtech/zbus" "github.com/threefoldtech/zos/pkg" - "github.com/threefoldtech/zos/pkg/network/types" ) -func TestNetworkProvision(t *testing.T) { - require := require.New(t) - - var client TestClient - ctx := context.Background() - ctx = WithZBus(ctx, &client) - - const module = "network" - version := zbus.ObjectID{Name: "network", Version: "0.0.1"} - - network := pkg.Network{ - Name: "net1", - // we set netid here ourselves although it's set by the provision - // method just to make sure that assertion pass. - NetID: networkID("user", "net1"), - IPRange: types.MustParseIPNet("192.168.1.0/24"), - NetResources: []pkg.NetResource{ - { - NodeID: "node-1", - Subnet: types.MustParseIPNet("192.168.1.0/24"), - }, - }, - } - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: NetworkReservation, - Data: MustMarshal(t, network), - } - - client.On("Request", module, version, "CreateNR", network). - Return("ns", nil) - - err := networkProvisionImpl(ctx, &reservation) - require.NoError(err) -} - -func TestNetworkDecommission(t *testing.T) { - require := require.New(t) - - var client TestClient - ctx := context.Background() - ctx = WithZBus(ctx, &client) - - const module = "network" - version := zbus.ObjectID{Name: "network", Version: "0.0.1"} - - network := pkg.Network{ - Name: "net1", - // we set netid here ourselves although it's set by the provision - // method just to make sure that assertion pass. - NetID: networkID("user", "net1"), - IPRange: types.MustParseIPNet("192.168.1.0/24"), - NetResources: []pkg.NetResource{ - { - NodeID: "node-1", - Subnet: types.MustParseIPNet("192.168.1.0/24"), - }, - }, - } - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: NetworkReservation, - Data: MustMarshal(t, network), - } - - client.On("Request", module, version, "DeleteNR", network). - Return(nil) - - err := networkDecommission(ctx, &reservation) - require.NoError(err) -} +// func TestNetworkProvision(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) + +// const module = "network" +// version := zbus.ObjectID{Name: "network", Version: "0.0.1"} + +// network := pkg.Network{ +// Name: "net1", +// // we set netid here ourselves although it's set by the provision +// // method just to make sure that assertion pass. +// NetID: networkID("user", "net1"), +// IPRange: types.MustParseIPNet("192.168.1.0/24"), +// NetResources: []pkg.NetResource{ +// { +// NodeID: "node-1", +// Subnet: types.MustParseIPNet("192.168.1.0/24"), +// }, +// }, +// } + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: NetworkReservation, +// Data: MustMarshal(t, network), +// } + +// client.On("Request", module, version, "CreateNR", network). +// Return("ns", nil) + +// err := networkProvisionImpl(ctx, &reservation) +// require.NoError(err) +// } + +// func TestNetworkDecommission(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) + +// const module = "network" +// version := zbus.ObjectID{Name: "network", Version: "0.0.1"} + +// network := pkg.Network{ +// Name: "net1", +// // we set netid here ourselves although it's set by the provision +// // method just to make sure that assertion pass. +// NetID: networkID("user", "net1"), +// IPRange: types.MustParseIPNet("192.168.1.0/24"), +// NetResources: []pkg.NetResource{ +// { +// NodeID: "node-1", +// Subnet: types.MustParseIPNet("192.168.1.0/24"), +// }, +// }, +// } + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: NetworkReservation, +// Data: MustMarshal(t, network), +// } + +// client.On("Request", module, version, "DeleteNR", network). +// Return(nil) + +// err := networkDecommission(ctx, &reservation) +// require.NoError(err) +// } func Test_networkID(t *testing.T) { type args struct { diff --git a/pkg/provision/primitives/provisioner.go b/pkg/provision/primitives/provisioner.go index b159f29f7..661af705f 100644 --- a/pkg/provision/primitives/provisioner.go +++ b/pkg/provision/primitives/provisioner.go @@ -5,6 +5,8 @@ import ( "github.com/threefoldtech/zos/pkg/provision" ) +// Provisioner hold all the logic responsible to provision and decomission +// the different primitives workloads defined by this package type Provisioner struct { cache provision.ReservationCache zbus zbus.Client @@ -13,6 +15,7 @@ type Provisioner struct { Decommissioners map[provision.ReservationType]provision.DecomissionerFunc } +// NewProvisioner creates a new 0-OS provisioner func NewProvisioner(cache provision.ReservationCache, zbus zbus.Client) *Provisioner { p := &Provisioner{ cache: cache, diff --git a/pkg/provision/primitives/volume_test.go b/pkg/provision/primitives/volume_test.go index 451b1fc8e..cce27907f 100644 --- a/pkg/provision/primitives/volume_test.go +++ b/pkg/provision/primitives/volume_test.go @@ -1,91 +1,82 @@ package primitives -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - "github.com/threefoldtech/zbus" - "github.com/threefoldtech/zos/pkg" -) - -func TestVolumeProvisionExists(t *testing.T) { - require := require.New(t) - - var client TestClient - ctx := context.Background() - ctx = WithZBus(ctx, &client) - - const module = "storage" - version := zbus.ObjectID{Name: "storage", Version: "0.0.1"} - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: VolumeReservation, - Data: MustMarshal(t, Volume{}), - } - - client.On("Request", module, version, "Path", reservation.ID). - Return("/some/path", nil) - - result, err := volumeProvisionImpl(ctx, &reservation) - require.NoError(err) - require.EqualValues(VolumeResult{"reservation-id"}, result) -} - -func TestVolumeProvisionNew(t *testing.T) { - require := require.New(t) - - var client TestClient - ctx := context.Background() - ctx = WithZBus(ctx, &client) - - const module = "storage" - version := zbus.ObjectID{Name: "storage", Version: "0.0.1"} - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: VolumeReservation, - Data: MustMarshal(t, Volume{ - Size: 10, - Type: SSDDiskType, - }), - } - - // force creation by returning an error - client.On("Request", module, version, "Path", reservation.ID). - Return("", zbus.RemoteError{"does not exist"}) - - client.On("Request", module, version, "CreateFilesystem", reservation.ID, 10*gigabyte, pkg.DeviceType(SSDDiskType)). - Return("/some/path", nil) - - result, err := volumeProvisionImpl(ctx, &reservation) - require.NoError(err) - require.EqualValues(VolumeResult{"reservation-id"}, result) -} - -func TestVolumeDecomission(t *testing.T) { - require := require.New(t) - - var client TestClient - ctx := context.Background() - ctx = WithZBus(ctx, &client) - - const module = "storage" - version := zbus.ObjectID{Name: "storage", Version: "0.0.1"} - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: VolumeReservation, - } - - // force decomission by returning a nil error - client.On("Request", module, version, "ReleaseFilesystem", reservation.ID). - Return(nil) - - err := volumeDecommission(ctx, &reservation) - require.NoError(err) -} +// func TestVolumeProvisionExists(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) + +// const module = "storage" +// version := zbus.ObjectID{Name: "storage", Version: "0.0.1"} + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: VolumeReservation, +// Data: MustMarshal(t, Volume{}), +// } + +// client.On("Request", module, version, "Path", reservation.ID). +// Return("/some/path", nil) + +// result, err := volumeProvisionImpl(ctx, &reservation) +// require.NoError(err) +// require.EqualValues(VolumeResult{"reservation-id"}, result) +// } + +// func TestVolumeProvisionNew(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) + +// const module = "storage" +// version := zbus.ObjectID{Name: "storage", Version: "0.0.1"} + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: VolumeReservation, +// Data: MustMarshal(t, Volume{ +// Size: 10, +// Type: SSDDiskType, +// }), +// } + +// // force creation by returning an error +// client.On("Request", module, version, "Path", reservation.ID). +// Return("", zbus.RemoteError{"does not exist"}) + +// client.On("Request", module, version, "CreateFilesystem", reservation.ID, 10*gigabyte, pkg.DeviceType(SSDDiskType)). +// Return("/some/path", nil) + +// result, err := volumeProvisionImpl(ctx, &reservation) +// require.NoError(err) +// require.EqualValues(VolumeResult{"reservation-id"}, result) +// } + +// func TestVolumeDecomission(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) + +// const module = "storage" +// version := zbus.ObjectID{Name: "storage", Version: "0.0.1"} + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: VolumeReservation, +// } + +// // force decomission by returning a nil error +// client.On("Request", module, version, "ReleaseFilesystem", reservation.ID). +// Return(nil) + +// err := volumeDecommission(ctx, &reservation) +// require.NoError(err) +// } diff --git a/pkg/provision/primitives/zdb_test.go b/pkg/provision/primitives/zdb_test.go index 2fbd57171..f67441ba8 100644 --- a/pkg/provision/primitives/zdb_test.go +++ b/pkg/provision/primitives/zdb_test.go @@ -1,19 +1,7 @@ package primitives import ( - "context" - "encoding/hex" - "fmt" - "net" - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/threefoldtech/zbus" - "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/zdb" ) @@ -51,245 +39,245 @@ func (c *zdbTestClient) Exist(name string) (bool, error) { return args.Bool(0), args.Error(1) } -func TestZDBProvision(t *testing.T) { - require := require.New(t) - - var client TestClient - ctx := context.Background() - ctx = WithZBus(ctx, &client) - - passwd := "pa$$w0rd" - conf := ZDB{ - Size: 280682, - Mode: pkg.ZDBModeSeq, - Password: hex.EncodeToString([]byte(passwd)), - DiskType: pkg.SSDDevice, - Public: true, - } - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: ZDBReservation, - Data: MustMarshal(t, conf), - } - - /* - Request(string,zbus.ObjectID,string,string,pkg.DeviceType,uint64,pkg.ZDBMode) - 0: "storage" - 1: zbus.ObjectID{Name:"storage", Version:"0.0.1"} - 2: "Allocate" - 3: "reservation-id" - 4: "SSD" - 5: 0x1121a80000000 - 6: "seq" - - */ - client.On("Request", "storage", zbus.ObjectID{Name: "storage", Version: "0.0.1"}, - "Allocate", "reservation-id", conf.DiskType, conf.Size*gigabyte, conf.Mode). - Return(pkg.Allocation{ - VolumeID: "zdb-container-id", - VolumePath: "/tmp/volume/path", - }, nil) - - client.On("Request", "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Inspect", "zdb", pkg.ContainerID("zdb-container-id")). - Return(pkg.Container{ - Name: "volume-id", - Network: pkg.NetworkInfo{ - Namespace: "net-ns", - }, - }, nil) - - client.On("Request", "identityd", zbus.ObjectID{Name: "manager", Version: "0.0.1"}, - "Decrypt", []byte(passwd)). - Return("password", nil) - - client.On("Request", "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, - "Addrs", - "zdb0", "net-ns").Return([]net.IP{net.ParseIP("2001:cdba::3257:9652")}, nil) - - var zdbClient zdbTestClient - zdbClient.On("Exist", reservation.ID).Return(false, nil) - zdbClient.On("CreateNamespace", reservation.ID).Return(nil) - zdbClient.On("NamespaceSetPassword", reservation.ID, "password").Return(nil) - zdbClient.On("NamespaceSetPublic", reservation.ID, conf.Public).Return(nil) - zdbClient.On("NamespaceSetSize", reservation.ID, conf.Size*gigabyte).Return(nil) - - zdbConnection = func(id pkg.ContainerID) zdb.Client { - return &zdbClient - } - - result, err := zdbProvisionImpl(ctx, &reservation) - - require.NoError(err) - require.Equal(reservation.ID, result.Namespace) - require.Equal("2001:cdba::3257:9652", result.IP) - require.EqualValues(zdbPort, result.Port) -} - -func TestZDBProvisionNoMappingContainerDoesNotExists(t *testing.T) { - require := require.New(t) - - var client TestClient - ctx := context.Background() - ctx = WithZBus(ctx, &client) - - passwd := "pa$$w0rd" - zdb := ZDB{ - Size: 10, - Mode: pkg.ZDBModeSeq, - Password: hex.EncodeToString([]byte(passwd)), - DiskType: pkg.SSDDevice, - Public: true, - } - - reservation := Reservation{ - ID: "reservation-id", - User: "user", - Type: ZDBReservation, - Data: MustMarshal(t, zdb), - } - - // return false on cache force creation - - // it's followed by allocation request to see if there is already a - // zdb instance running that has enough space for the ns - - client.On("Request", "identityd", zbus.ObjectID{Name: "manager", Version: "0.0.1"}, - "Decrypt", []byte(passwd)). - Return("password", nil) - - client.On("Request", "storage", zbus.ObjectID{Name: "storage", Version: "0.0.1"}, - "Allocate", - reservation.ID, zdb.DiskType, zdb.Size*gigabyte, zdb.Mode, - ).Return(pkg.Allocation{ - VolumeID: "container-id", - VolumePath: "/path/to/volume", - }, nil) - - // container does NOT exists, so inspect still should return an error - client.On("Request", "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Inspect", "zdb", pkg.ContainerID("container-id")). - Return(pkg.Container{}, fmt.Errorf("not found")) - - client.On("Request", "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, - "Addrs", - "zdb0", "net-ns").Return([]net.IP{net.ParseIP("2001:cdba::3257:9652")}, nil) - - client.On("Request", "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, - "Mount", - "https://hub.grid.tf/tf-autobuilder/threefoldtech-0-db-development.flist", - "", pkg.MountOptions{ - Limit: 10, - ReadOnly: false, - }).Return("/path/to/volume", nil) - - // The hw address in this mock call, is based on the VolumeID above. changing the VolumeID - // will require this HW address to change - client.On("Request", "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, - "ZDBPrepare", []byte{0x7e, 0xcc, 0xc3, 0x81, 0xa2, 0x2e}).Return("net-ns", nil) - - client.On("Request", "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, - "Run", - "zdb", - pkg.Container{ - Name: "container-id", - RootFS: "/path/to/volume", - Network: pkg.NetworkInfo{Namespace: "net-ns"}, - Mounts: []pkg.MountInfo{ - pkg.MountInfo{ - Source: "/path/to/volume", - Target: "/data", - }, - pkg.MountInfo{ - Source: "/var/run/zdb_container-id", - Target: "/socket", - }, - }, - Entrypoint: "/bin/zdb --data /data --index /data --mode seq --listen :: --port 9900 --socket /socket/zdb.sock --dualnet", - Interactive: false, - }, - ).Return(pkg.ContainerID("container-id"), nil) - - _, err := zdbProvisionImpl(ctx, &reservation) - - // unfortunately the provision will try (as last step) to dial - // the unix socket of the server, and then actually configure - // the namespace and set the limits, password, etc... - // for now we just handle the connection error and assume - // it succeeded. - // TODO: start a mock server on this address - require.Error(err) - require.True(strings.Contains(err.Error(), "/var/run/zdb_container-id")) -} - -func Test_findDataVolume(t *testing.T) { - type args struct { - mounts []pkg.MountInfo - } - tests := []struct { - name string - args args - want string - wantErr bool - }{ - { - name: "exists", - args: args{ - mounts: []pkg.MountInfo{ - { - Target: "/data", - Source: "/mnt/sda/subovl1", - }, - { - Target: "/etc/resolv.conf", - Source: "/etc/resolv.conf", - }, - }, - }, - want: "subovl1", - wantErr: false, - }, - { - name: "non-exists", - args: args{ - mounts: []pkg.MountInfo{ - { - Target: "/etc/resolv.conf", - Source: "/etc/resolv.conf", - }, - }, - }, - want: "", - wantErr: true, - }, - { - name: "empty", - args: args{ - mounts: []pkg.MountInfo{}, - }, - want: "", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := findDataVolume(tt.args.mounts) - if tt.wantErr { - require.Error(t, err) - } else { - assert.Equal(t, tt.want, got) - } - }) - } -} - -func findDataVolume(mounts []pkg.MountInfo) (string, error) { - for _, mount := range mounts { - if mount.Target == "/data" { - return filepath.Base(mount.Source), nil - } - } - return "", fmt.Errorf("data volume not found") -} +// func TestZDBProvision(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) + +// passwd := "pa$$w0rd" +// conf := ZDB{ +// Size: 280682, +// Mode: pkg.ZDBModeSeq, +// Password: hex.EncodeToString([]byte(passwd)), +// DiskType: pkg.SSDDevice, +// Public: true, +// } + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: ZDBReservation, +// Data: MustMarshal(t, conf), +// } + +// /* +// Request(string,zbus.ObjectID,string,string,pkg.DeviceType,uint64,pkg.ZDBMode) +// 0: "storage" +// 1: zbus.ObjectID{Name:"storage", Version:"0.0.1"} +// 2: "Allocate" +// 3: "reservation-id" +// 4: "SSD" +// 5: 0x1121a80000000 +// 6: "seq" + +// */ +// client.On("Request", "storage", zbus.ObjectID{Name: "storage", Version: "0.0.1"}, +// "Allocate", "reservation-id", conf.DiskType, conf.Size*gigabyte, conf.Mode). +// Return(pkg.Allocation{ +// VolumeID: "zdb-container-id", +// VolumePath: "/tmp/volume/path", +// }, nil) + +// client.On("Request", "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Inspect", "zdb", pkg.ContainerID("zdb-container-id")). +// Return(pkg.Container{ +// Name: "volume-id", +// Network: pkg.NetworkInfo{ +// Namespace: "net-ns", +// }, +// }, nil) + +// client.On("Request", "identityd", zbus.ObjectID{Name: "manager", Version: "0.0.1"}, +// "Decrypt", []byte(passwd)). +// Return("password", nil) + +// client.On("Request", "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, +// "Addrs", +// "zdb0", "net-ns").Return([]net.IP{net.ParseIP("2001:cdba::3257:9652")}, nil) + +// var zdbClient zdbTestClient +// zdbClient.On("Exist", reservation.ID).Return(false, nil) +// zdbClient.On("CreateNamespace", reservation.ID).Return(nil) +// zdbClient.On("NamespaceSetPassword", reservation.ID, "password").Return(nil) +// zdbClient.On("NamespaceSetPublic", reservation.ID, conf.Public).Return(nil) +// zdbClient.On("NamespaceSetSize", reservation.ID, conf.Size*gigabyte).Return(nil) + +// zdbConnection = func(id pkg.ContainerID) zdb.Client { +// return &zdbClient +// } + +// result, err := zdbProvisionImpl(ctx, &reservation) + +// require.NoError(err) +// require.Equal(reservation.ID, result.Namespace) +// require.Equal("2001:cdba::3257:9652", result.IP) +// require.EqualValues(zdbPort, result.Port) +// } + +// func TestZDBProvisionNoMappingContainerDoesNotExists(t *testing.T) { +// require := require.New(t) + +// var client TestClient +// ctx := context.Background() +// ctx = WithZBus(ctx, &client) + +// passwd := "pa$$w0rd" +// zdb := ZDB{ +// Size: 10, +// Mode: pkg.ZDBModeSeq, +// Password: hex.EncodeToString([]byte(passwd)), +// DiskType: pkg.SSDDevice, +// Public: true, +// } + +// reservation := Reservation{ +// ID: "reservation-id", +// User: "user", +// Type: ZDBReservation, +// Data: MustMarshal(t, zdb), +// } + +// // return false on cache force creation + +// // it's followed by allocation request to see if there is already a +// // zdb instance running that has enough space for the ns + +// client.On("Request", "identityd", zbus.ObjectID{Name: "manager", Version: "0.0.1"}, +// "Decrypt", []byte(passwd)). +// Return("password", nil) + +// client.On("Request", "storage", zbus.ObjectID{Name: "storage", Version: "0.0.1"}, +// "Allocate", +// reservation.ID, zdb.DiskType, zdb.Size*gigabyte, zdb.Mode, +// ).Return(pkg.Allocation{ +// VolumeID: "container-id", +// VolumePath: "/path/to/volume", +// }, nil) + +// // container does NOT exists, so inspect still should return an error +// client.On("Request", "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Inspect", "zdb", pkg.ContainerID("container-id")). +// Return(pkg.Container{}, fmt.Errorf("not found")) + +// client.On("Request", "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, +// "Addrs", +// "zdb0", "net-ns").Return([]net.IP{net.ParseIP("2001:cdba::3257:9652")}, nil) + +// client.On("Request", "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, +// "Mount", +// "https://hub.grid.tf/tf-autobuilder/threefoldtech-0-db-development.flist", +// "", pkg.MountOptions{ +// Limit: 10, +// ReadOnly: false, +// }).Return("/path/to/volume", nil) + +// // The hw address in this mock call, is based on the VolumeID above. changing the VolumeID +// // will require this HW address to change +// client.On("Request", "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, +// "ZDBPrepare", []byte{0x7e, 0xcc, 0xc3, 0x81, 0xa2, 0x2e}).Return("net-ns", nil) + +// client.On("Request", "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, +// "Run", +// "zdb", +// pkg.Container{ +// Name: "container-id", +// RootFS: "/path/to/volume", +// Network: pkg.NetworkInfo{Namespace: "net-ns"}, +// Mounts: []pkg.MountInfo{ +// pkg.MountInfo{ +// Source: "/path/to/volume", +// Target: "/data", +// }, +// pkg.MountInfo{ +// Source: "/var/run/zdb_container-id", +// Target: "/socket", +// }, +// }, +// Entrypoint: "/bin/zdb --data /data --index /data --mode seq --listen :: --port 9900 --socket /socket/zdb.sock --dualnet", +// Interactive: false, +// }, +// ).Return(pkg.ContainerID("container-id"), nil) + +// _, err := zdbProvisionImpl(ctx, &reservation) + +// // unfortunately the provision will try (as last step) to dial +// // the unix socket of the server, and then actually configure +// // the namespace and set the limits, password, etc... +// // for now we just handle the connection error and assume +// // it succeeded. +// // TODO: start a mock server on this address +// require.Error(err) +// require.True(strings.Contains(err.Error(), "/var/run/zdb_container-id")) +// } + +// func Test_findDataVolume(t *testing.T) { +// type args struct { +// mounts []pkg.MountInfo +// } +// tests := []struct { +// name string +// args args +// want string +// wantErr bool +// }{ +// { +// name: "exists", +// args: args{ +// mounts: []pkg.MountInfo{ +// { +// Target: "/data", +// Source: "/mnt/sda/subovl1", +// }, +// { +// Target: "/etc/resolv.conf", +// Source: "/etc/resolv.conf", +// }, +// }, +// }, +// want: "subovl1", +// wantErr: false, +// }, +// { +// name: "non-exists", +// args: args{ +// mounts: []pkg.MountInfo{ +// { +// Target: "/etc/resolv.conf", +// Source: "/etc/resolv.conf", +// }, +// }, +// }, +// want: "", +// wantErr: true, +// }, +// { +// name: "empty", +// args: args{ +// mounts: []pkg.MountInfo{}, +// }, +// want: "", +// wantErr: true, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// got, err := findDataVolume(tt.args.mounts) +// if tt.wantErr { +// require.Error(t, err) +// } else { +// assert.Equal(t, tt.want, got) +// } +// }) +// } +// } + +// func findDataVolume(mounts []pkg.MountInfo) (string, error) { +// for _, mount := range mounts { +// if mount.Target == "/data" { +// return filepath.Base(mount.Source), nil +// } +// } +// return "", fmt.Errorf("data volume not found") +// } diff --git a/pkg/provision/resource_units.go b/pkg/provision/resource_units.go index ec519fc1d..e1779546f 100644 --- a/pkg/provision/resource_units.go +++ b/pkg/provision/resource_units.go @@ -3,6 +3,8 @@ package provision // ResourceUnits type type ResourceUnits string +// ResourcesUnits are the units used to compute how much +// capacity is reserved on the system var ( ResourceUnitsCRU = ResourceUnits("CRU") ResourceUnitsMRU = ResourceUnits("MRU") diff --git a/pkg/provision/source.go b/pkg/provision/source.go index e1f880384..f1672f56d 100644 --- a/pkg/provision/source.go +++ b/pkg/provision/source.go @@ -10,7 +10,6 @@ import ( "github.com/rs/zerolog/log" "github.com/threefoldtech/tfexplorer/client" - "github.com/threefoldtech/tfexplorer/models/generated/workloads" "github.com/threefoldtech/zos/pkg" ) @@ -31,10 +30,6 @@ type ReservationPoller interface { Poll(nodeID pkg.Identifier, from uint64) ([]*Reservation, error) } -// ReservationConverterFunc is used to convert from the explorer workloads type into the -// internal Reservation type -type ReservationConverterFunc func(w workloads.ReservationWorkload) (*Reservation, error) - // PollSource does a long poll on address to get new and to be deleted // reservations. the server should only return unique reservations // stall the connection as long as possible if no new reservations From 47d5d485b493afb5d3d8e93a471e8056b8c3f9fd Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Sun, 19 Apr 2020 16:33:19 +0200 Subject: [PATCH 07/14] more cleanup and linting warnings --- cmds/provisiond/main.go | 9 +- .../{primitives => }/expiration_test.go | 10 +- .../{local_store.go => cache/cache.go} | 85 +--- .../cache_test.go} | 4 +- pkg/provision/primitives/container_test.go | 415 ------------------ pkg/provision/primitives/network_test.go | 119 ----- pkg/provision/primitives/provision_test.go | 66 --- pkg/provision/primitives/volume_test.go | 82 ---- pkg/provision/primitives/zdb_test.go | 283 ------------ 9 files changed, 33 insertions(+), 1040 deletions(-) rename pkg/provision/{primitives => }/expiration_test.go (77%) rename pkg/provision/primitives/{local_store.go => cache/cache.go} (71%) rename pkg/provision/primitives/{local_store_test.go => cache/cache_test.go} (97%) delete mode 100644 pkg/provision/primitives/container_test.go delete mode 100644 pkg/provision/primitives/network_test.go delete mode 100644 pkg/provision/primitives/provision_test.go delete mode 100644 pkg/provision/primitives/volume_test.go delete mode 100644 pkg/provision/primitives/zdb_test.go diff --git a/cmds/provisiond/main.go b/cmds/provisiond/main.go index 884995743..ff6a87c10 100644 --- a/cmds/provisiond/main.go +++ b/cmds/provisiond/main.go @@ -12,6 +12,7 @@ import ( "github.com/threefoldtech/zos/pkg/environment" "github.com/threefoldtech/zos/pkg/provision/explorer" "github.com/threefoldtech/zos/pkg/provision/primitives" + "github.com/threefoldtech/zos/pkg/provision/primitives/cache" "github.com/threefoldtech/zos/pkg/stubs" "github.com/threefoldtech/zos/pkg/utils" @@ -92,7 +93,7 @@ func main() { nodeID := identity.NodeID() // to get reservation from tnodb - cl, err := app.ExplorerClient() + e, err := app.ExplorerClient() if err != nil { log.Fatal().Err(err).Msg("failed to instantiate BCDB client") } @@ -101,7 +102,7 @@ func main() { statser := &primitives.Counters{} // to store reservation locally on the node - localStore, err := primitives.NewFSStore(filepath.Join(storageDir, "reservations")) + localStore, err := cache.NewFSStore(filepath.Join(storageDir, "reservations")) if err != nil { log.Fatal().Err(err).Msg("failed to create local reservation store") } @@ -114,12 +115,12 @@ func main() { NodeID: nodeID.Identity(), Cache: localStore, Source: provision.CombinedSource( - provision.PollSource(explorer.ReservationPollerFromWorkloads(cl.Workloads, primitives.WorkloadToProvisionType, primitives.ProvisionOrder), nodeID), + provision.PollSource(explorer.NewPoller(e, primitives.WorkloadToProvisionType, primitives.ProvisionOrder), nodeID), provision.NewDecommissionSource(localStore), ), Provisioners: provisioner.Provisioners, Decomissioners: provisioner.Decommissioners, - Feedback: explorer.NewExplorerFeedback(cl, primitives.ResultToSchemaType), + Feedback: explorer.NewFeedback(e, primitives.ResultToSchemaType), Signer: identity, Statser: statser, }) diff --git a/pkg/provision/primitives/expiration_test.go b/pkg/provision/expiration_test.go similarity index 77% rename from pkg/provision/primitives/expiration_test.go rename to pkg/provision/expiration_test.go index d62f94d48..3053664cc 100644 --- a/pkg/provision/primitives/expiration_test.go +++ b/pkg/provision/expiration_test.go @@ -1,15 +1,13 @@ -package primitives +package provision import ( "testing" "time" - - "github.com/threefoldtech/zos/pkg/provision" ) func TestExpired(t *testing.T) { type args struct { - r *provision.Reservation + r *Reservation } tests := []struct { name string @@ -18,7 +16,7 @@ func TestExpired(t *testing.T) { }{ { name: "expired", - args: args{&provision.Reservation{ + args: args{&Reservation{ Created: time.Now().Add(-time.Minute), Duration: time.Second, }}, @@ -26,7 +24,7 @@ func TestExpired(t *testing.T) { }, { name: "not expired", - args: args{&provision.Reservation{ + args: args{&Reservation{ Created: time.Now(), Duration: time.Minute, }}, diff --git a/pkg/provision/primitives/local_store.go b/pkg/provision/primitives/cache/cache.go similarity index 71% rename from pkg/provision/primitives/local_store.go rename to pkg/provision/primitives/cache/cache.go index 615799302..a73c575cf 100644 --- a/pkg/provision/primitives/local_store.go +++ b/pkg/provision/primitives/cache/cache.go @@ -1,4 +1,4 @@ -package primitives +package cache import ( "encoding/json" @@ -13,6 +13,7 @@ import ( "github.com/rs/zerolog/log" "github.com/threefoldtech/zos/pkg/app" "github.com/threefoldtech/zos/pkg/provision" + "github.com/threefoldtech/zos/pkg/provision/primitives" "github.com/threefoldtech/zos/pkg/versioned" ) @@ -23,16 +24,15 @@ var ( reservationSchemaLastVersion = reservationSchemaV1 ) -// FSStore is a in reservation store -// using the filesystem as backend -type FSStore struct { +// Fs is a in reservation cache using the filesystem as backend +type Fs struct { sync.RWMutex root string } // NewFSStore creates a in memory reservation store -func NewFSStore(root string) (*FSStore, error) { - store := &FSStore{ +func NewFSStore(root string) (*Fs, error) { + store := &Fs{ root: root, } if app.IsFirstBoot("provisiond") { @@ -57,7 +57,7 @@ func NewFSStore(root string) (*FSStore, error) { //TODO: i think both sync and removeAllButPersistent can be merged into // one method because now it scans the same directory twice. -func (s *FSStore) removeAllButPersistent(rootPath string) error { +func (s *Fs) removeAllButPersistent(rootPath string) error { // if rootPath is not present on the filesystem, return _, err := os.Stat(rootPath) if os.IsNotExist(err) { @@ -83,7 +83,7 @@ func (s *FSStore) removeAllButPersistent(rootPath string) error { if err != nil { return err } - if reservationType != VolumeReservation && reservationType != ZDBReservation { + if reservationType != primitives.VolumeReservation && reservationType != primitives.ZDBReservation { log.Info().Msgf("Removing %s from cache", path) return os.Remove(path) } @@ -97,7 +97,7 @@ func (s *FSStore) removeAllButPersistent(rootPath string) error { } // Sync update the statser with all the reservation present in the cache -func (s *FSStore) Sync(statser provision.Statser) error { +func (s *Fs) Sync(statser provision.Statser) error { //this should probably be reversed and moved to the Statser object instead s.RLock() @@ -125,7 +125,7 @@ func (s *FSStore) Sync(statser provision.Statser) error { } // Add a reservation to the store -func (s *FSStore) Add(r *provision.Reservation) error { +func (s *Fs) Add(r *provision.Reservation) error { s.Lock() defer s.Unlock() @@ -151,44 +151,28 @@ func (s *FSStore) Add(r *provision.Reservation) error { return err } - // s.counterFor(r.Type).Increment(1) - // if err := s.processResourceUnits(r, true); err != nil { - // return errors.Wrapf(err, "could not compute the amount of resource used by reservation %s", r.ID) - // } - return nil } // Remove a reservation from the store -func (s *FSStore) Remove(id string) error { +func (s *Fs) Remove(id string) error { s.Lock() defer s.Unlock() - // r, err := s.get(id) - // if os.IsNotExist(errors.Cause(err)) { - // return nil - // } - path := filepath.Join(s.root, id) err := os.Remove(path) - if os.IsNotExist(err) { - // shouldn't happen because we just did a get + if os.IsNotExist(errors.Cause(err)) { return nil } else if err != nil { return err } - // s.counterFor(r.Type).Decrement(1) - // if err := s.processResourceUnits(r, false); err != nil { - // return errors.Wrapf(err, "could not compute the amount of resource used by reservation %s", r.ID) - // } - return nil } // GetExpired returns all id the the reservations that are expired // at the time of the function call -func (s *FSStore) GetExpired() ([]*provision.Reservation, error) { +func (s *Fs) GetExpired() ([]*provision.Reservation, error) { s.RLock() defer s.RUnlock() @@ -227,7 +211,7 @@ func (s *FSStore) GetExpired() ([]*provision.Reservation, error) { // Get retrieves a specific reservation using its ID // if returns a non nil error if the reservation is not present in the store -func (s *FSStore) Get(id string) (*provision.Reservation, error) { +func (s *Fs) Get(id string) (*provision.Reservation, error) { s.RLock() defer s.RUnlock() @@ -236,41 +220,16 @@ func (s *FSStore) Get(id string) (*provision.Reservation, error) { // getType retrieves a specific reservation's type using its ID // if returns a non nil error if the reservation is not present in the store -func (s *FSStore) getType(id string) (provision.ReservationType, error) { - res := struct { - Type provision.ReservationType `json:"type"` - }{} - path := filepath.Join(s.root, id) - f, err := os.Open(path) - if os.IsNotExist(err) { - return "", errors.Wrapf(err, "reservation %s not found", id) - } else if err != nil { - return "", err - } - - defer f.Close() - reader, err := versioned.NewReader(f) - if versioned.IsNotVersioned(err) { - if _, err := f.Seek(0, 0); err != nil { // make sure to read from start - return "", err - } - reader = versioned.NewVersionedReader(versioned.MustParse("0.0.0"), f) - } - - validV1 := versioned.MustParseRange(fmt.Sprintf("<=%s", reservationSchemaV1)) - - if validV1(reader.Version()) { - if err := json.NewDecoder(reader).Decode(&res); err != nil { - return "nil", err - } - } else { - return "", fmt.Errorf("unknown reservation object version (%s)", reader.Version()) +func (s *Fs) getType(id string) (provision.ReservationType, error) { + r, err := s.get(id) + if err != nil { + return provision.ReservationType(0), err } - return res.Type, nil + return r.Type, nil } // Exists checks if the reservation ID is in the store -func (s *FSStore) Exists(id string) (bool, error) { +func (s *Fs) Exists(id string) (bool, error) { s.RLock() defer s.RUnlock() @@ -285,7 +244,7 @@ func (s *FSStore) Exists(id string) (bool, error) { return false, err } -func (s *FSStore) get(id string) (*provision.Reservation, error) { +func (s *Fs) get(id string) (*provision.Reservation, error) { path := filepath.Join(s.root, id) f, err := os.Open(path) if os.IsNotExist(err) { @@ -318,6 +277,6 @@ func (s *FSStore) get(id string) (*provision.Reservation, error) { } // Close makes sure the backend of the store is closed properly -func (s *FSStore) Close() error { +func (s *Fs) Close() error { return nil } diff --git a/pkg/provision/primitives/local_store_test.go b/pkg/provision/primitives/cache/cache_test.go similarity index 97% rename from pkg/provision/primitives/local_store_test.go rename to pkg/provision/primitives/cache/cache_test.go index 35e72ac0a..a884141b0 100644 --- a/pkg/provision/primitives/local_store_test.go +++ b/pkg/provision/primitives/cache/cache_test.go @@ -1,4 +1,4 @@ -package primitives +package cache import ( "io/ioutil" @@ -45,7 +45,7 @@ func TestLocalStore(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := &FSStore{ + s := &Fs{ root: tt.fields.root, } diff --git a/pkg/provision/primitives/container_test.go b/pkg/provision/primitives/container_test.go deleted file mode 100644 index 0b5ea9dfe..000000000 --- a/pkg/provision/primitives/container_test.go +++ /dev/null @@ -1,415 +0,0 @@ -package primitives - -// func TestContainerProvisionExists(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// var cache TestOwnerCache - -// p := NewProvisioner(cache, client) -// ctx := context.Background() - -// // const module = "network" -// // version := zbus.ObjectID{Name: "network", Version: "0.0.1"} - -// container := Container{ -// FList: "https://hub.grid.tf/thabet/redis.flist", -// Network: Network{ -// NetworkID: pkg.NetID("net1"), -// IPs: []net.IP{ -// net.ParseIP("192.168.1.1"), -// }, -// }, -// } - -// reservation := provision.Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: ContainerReservation, -// Data: MustMarshal(t, container), -// } - -// // first, the provision will inspect the container -// client.On("Request", -// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Inspect", -// fmt.Sprintf("ns%s", reservation.User), -// pkg.ContainerID(reservation.ID)). -// Return(pkg.Container{Name: reservation.ID}, nil) - -// result, err := p.containerProvisionImpl(ctx, &reservation) -// require.NoError(err) -// require.Equal("reservation-id", result.ID) -// } - -// func TestContainerProvisionNew(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// var cache TestOwnerCache - -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) -// ctx = WithOwnerCache(ctx, &cache) - -// // const module = "network" -// // version := zbus.ObjectID{Name: "network", Version: "0.0.1"} - -// container := Container{ -// FList: "https://hub.grid.tf/thabet/redis.flist", -// Network: Network{ -// NetworkID: pkg.NetID("net1"), -// IPs: []net.IP{ -// net.ParseIP("192.168.1.1"), -// }, -// PublicIP6: false, -// }, -// } - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: ContainerReservation, -// Data: MustMarshal(t, container), -// } - -// userNs := fmt.Sprintf("ns%s", reservation.User) -// // first, the provision will inspect the container -// // see if it exists -// client.On("Request", -// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Inspect", -// userNs, -// pkg.ContainerID(reservation.ID)). -// Return(pkg.Container{}, zbus.RemoteError{"does not exist"}) - -// netID := networkID(reservation.User, string(container.Network.NetworkID)) - -// // since it's a new container, a call to join the network is made -// client.On("Request", -// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, -// "Join", -// netID, reservation.ID, []string{"192.168.1.1"}, false). -// Return(pkg.Member{ -// Namespace: "net-ns", -// IPv4: net.ParseIP("192.168.1.1"), -// }, nil) - -// // then another call to mount the flist -// client.On("Request", -// "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, -// "Mount", -// container.FList, container.FlistStorage, pkg.DefaultMountOptions). -// Return("/tmp/root", nil) - -// // once root is mounted, a final call to contd to run the container -// client.On("Request", -// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Run", -// userNs, pkg.Container{ -// Name: reservation.ID, -// RootFS: "/tmp/root", //returned by flist.Mount -// Network: pkg.NetworkInfo{ -// Namespace: "net-ns", -// }, -// }). -// Return(pkg.ContainerID(reservation.ID), nil) - -// result, err := containerProvisionImpl(ctx, &reservation) -// require.NoError(err) -// require.Equal("reservation-id", result.ID) -// require.Equal("192.168.1.1", result.IPv4) -// } - -// func TestContainerProvisionWithMounts(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// var cache TestOwnerCache - -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) -// ctx = WithOwnerCache(ctx, &cache) - -// container := Container{ -// FList: "https://hub.grid.tf/thabet/redis.flist", -// Network: Network{ -// NetworkID: pkg.NetID("net1"), -// IPs: []net.IP{ -// net.ParseIP("192.168.1.1"), -// }, -// PublicIP6: false, -// }, -// Mounts: []Mount{ -// { -// VolumeID: "vol1", -// Mountpoint: "/opt", -// }, -// }, -// } - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: ContainerReservation, -// Data: MustMarshal(t, container), -// } - -// //NOTE: since we have moutns, a call is made to get the owner -// //of the volume to validate it's the same owner of the reservation -// cache.On("OwnerOf", "vol1").Return(reservation.User, nil) - -// userNs := fmt.Sprintf("ns%s", reservation.User) -// // first, the provision will inspect the container -// // see if it exists -// client.On("Request", -// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Inspect", -// userNs, -// pkg.ContainerID(reservation.ID)). -// Return(pkg.Container{}, zbus.RemoteError{"does not exist"}) - -// netID := networkID(reservation.User, string(container.Network.NetworkID)) - -// // since it's a new container, a call to join the network is made -// client.On("Request", -// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, -// "Join", -// netID, reservation.ID, []string{"192.168.1.1"}, false). -// Return(pkg.Member{ -// Namespace: "net-ns", -// IPv4: net.ParseIP("192.168.1.1"), -// }, nil) - -// // then another call to mount the flist -// client.On("Request", -// "storage", zbus.ObjectID{Name: "storage", Version: "0.0.1"}, -// "Path", -// "vol1"). -// Return("/some/path/to/vol1", nil) - -// //for each volume in the list, a call is made to get the mount path -// //of that volume -// client.On("Request", -// "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, -// "Mount", -// container.FList, container.FlistStorage, pkg.DefaultMountOptions). -// Return("/tmp/root", nil) - -// // once root is mounted, a final call to contd to run the container -// client.On("Request", -// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Run", -// userNs, pkg.Container{ -// Name: reservation.ID, -// RootFS: "/tmp/root", //returned by flist.Mount -// Network: pkg.NetworkInfo{ -// Namespace: "net-ns", -// }, -// Mounts: []pkg.MountInfo{ -// { -// Source: "/some/path/to/vol1", -// Target: "/opt", -// }, -// }, -// }). -// Return(pkg.ContainerID(reservation.ID), nil) - -// result, err := containerProvisionImpl(ctx, &reservation) -// require.NoError(err) -// require.Equal("reservation-id", result.ID) -// require.Equal("192.168.1.1", result.IPv4) -// } - -// func TestContainerDecomissionExists(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// var cache TestOwnerCache - -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) -// ctx = WithOwnerCache(ctx, &cache) - -// // const module = "network" -// // version := zbus.ObjectID{Name: "network", Version: "0.0.1"} - -// container := Container{ -// FList: "https://hub.grid.tf/thabet/redis.flist", -// Network: Network{ -// NetworkID: pkg.NetID("net1"), -// IPs: []net.IP{ -// net.ParseIP("192.168.1.1"), -// }, -// }, -// } - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: ContainerReservation, -// Data: MustMarshal(t, container), -// } -// rootFS := "/mnt/flist_root" - -// // first, the decomission will inspect the container -// client.On("Request", -// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Inspect", -// fmt.Sprintf("ns%s", reservation.User), -// pkg.ContainerID(reservation.ID)). -// Return(pkg.Container{Name: reservation.ID, RootFS: rootFS}, nil) - -// // second, the decomission will delete the container -// client.On("Request", -// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Delete", -// fmt.Sprintf("ns%s", reservation.User), -// pkg.ContainerID(reservation.ID)). -// Return(nil) - -// // third, unmount the flist of the container -// client.On("Request", -// "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, -// "Umount", -// rootFS). -// Return(nil) - -// netID := networkID(reservation.User, string(container.Network.NetworkID)) - -// // fourth, check if the network still exists -// client.On("Request", -// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, -// "GetSubnet", -// netID). -// Return(net.IPNet{}, nil) // return a nil error, the network still exists - -// // fifths, leave the container network namespace -// client.On("Request", -// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, -// "Leave", -// netID, -// reservation.ID). -// Return(nil) - -// err := containerDecommission(ctx, &reservation) -// require.NoError(err) -// } - -// func TestContainerDecomissionContainerGone(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// var cache TestOwnerCache - -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) -// ctx = WithOwnerCache(ctx, &cache) - -// container := Container{ -// FList: "https://hub.grid.tf/thabet/redis.flist", -// Network: Network{ -// NetworkID: pkg.NetID("net1"), -// IPs: []net.IP{ -// net.ParseIP("192.168.1.1"), -// }, -// }, -// } - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: ContainerReservation, -// Data: MustMarshal(t, container), -// } - -// // first, the decomission will inspect the container -// client.On("Request", -// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Inspect", -// fmt.Sprintf("ns%s", reservation.User), -// pkg.ContainerID(reservation.ID)). -// Return(nil, fmt.Errorf("container not found")) - -// // second, check if the network still exists -// netID := networkID(reservation.User, string(container.Network.NetworkID)) -// client.On("Request", -// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, -// "GetSubnet", -// netID). -// Return(net.IPNet{}, nil) // return a nil error, the network still exists - -// // third, leave the container network namespace -// client.On("Request", -// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, -// "Leave", -// netID, -// reservation.ID). -// Return(nil) - -// err := containerDecommission(ctx, &reservation) -// require.NoError(err) -// } - -// func TestContainerDecomissionNetworkGone(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// var cache TestOwnerCache - -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) -// ctx = WithOwnerCache(ctx, &cache) - -// container := Container{ -// FList: "https://hub.grid.tf/thabet/redis.flist", -// Network: Network{ -// NetworkID: pkg.NetID("net1"), -// IPs: []net.IP{ -// net.ParseIP("192.168.1.1"), -// }, -// }, -// } - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: ContainerReservation, -// Data: MustMarshal(t, container), -// } -// rootFS := "/mnt/flist_root" - -// // first, the decomission will inspect the container -// client.On("Request", -// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Inspect", -// fmt.Sprintf("ns%s", reservation.User), -// pkg.ContainerID(reservation.ID)). -// Return(pkg.Container{Name: reservation.ID, RootFS: rootFS}, nil) - -// // second, the decomission will delete the container -// client.On("Request", -// "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Delete", -// fmt.Sprintf("ns%s", reservation.User), -// pkg.ContainerID(reservation.ID)). -// Return(nil) - -// // third, unmount the flist of the container -// client.On("Request", -// "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, -// "Umount", -// rootFS). -// Return(nil) - -// netID := networkID(reservation.User, string(container.Network.NetworkID)) -// // fourth, check if the network still exists -// client.On("Request", -// "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, -// "GetSubnet", -// netID). -// Return(net.IPNet{}, fmt.Errorf("network not found")) - -// err := containerDecommission(ctx, &reservation) -// require.NoError(err) -// } diff --git a/pkg/provision/primitives/network_test.go b/pkg/provision/primitives/network_test.go deleted file mode 100644 index 5cebf7253..000000000 --- a/pkg/provision/primitives/network_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package primitives - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/threefoldtech/zos/pkg" -) - -// func TestNetworkProvision(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) - -// const module = "network" -// version := zbus.ObjectID{Name: "network", Version: "0.0.1"} - -// network := pkg.Network{ -// Name: "net1", -// // we set netid here ourselves although it's set by the provision -// // method just to make sure that assertion pass. -// NetID: networkID("user", "net1"), -// IPRange: types.MustParseIPNet("192.168.1.0/24"), -// NetResources: []pkg.NetResource{ -// { -// NodeID: "node-1", -// Subnet: types.MustParseIPNet("192.168.1.0/24"), -// }, -// }, -// } - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: NetworkReservation, -// Data: MustMarshal(t, network), -// } - -// client.On("Request", module, version, "CreateNR", network). -// Return("ns", nil) - -// err := networkProvisionImpl(ctx, &reservation) -// require.NoError(err) -// } - -// func TestNetworkDecommission(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) - -// const module = "network" -// version := zbus.ObjectID{Name: "network", Version: "0.0.1"} - -// network := pkg.Network{ -// Name: "net1", -// // we set netid here ourselves although it's set by the provision -// // method just to make sure that assertion pass. -// NetID: networkID("user", "net1"), -// IPRange: types.MustParseIPNet("192.168.1.0/24"), -// NetResources: []pkg.NetResource{ -// { -// NodeID: "node-1", -// Subnet: types.MustParseIPNet("192.168.1.0/24"), -// }, -// }, -// } - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: NetworkReservation, -// Data: MustMarshal(t, network), -// } - -// client.On("Request", module, version, "DeleteNR", network). -// Return(nil) - -// err := networkDecommission(ctx, &reservation) -// require.NoError(err) -// } - -func Test_networkID(t *testing.T) { - type args struct { - userID string - name string - } - tests := []struct { - name string - args args - want pkg.NetID - }{ - { - name: "net-1", - args: args{ - userID: "user1", - name: "net-1", - }, - want: pkg.NetID("EJyFexd14LVGi"), - }, - { - name: "net-2", - args: args{ - userID: "user1", - name: "net-2", - }, - want: pkg.NetID("4ftuPjY3wuvho"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := networkID(tt.args.userID, tt.args.name) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/provision/primitives/provision_test.go b/pkg/provision/primitives/provision_test.go deleted file mode 100644 index ac22f075b..000000000 --- a/pkg/provision/primitives/provision_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package primitives - -import ( - "context" - "encoding/json" - "testing" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/threefoldtech/zbus" -) - -func MustMarshal(t *testing.T, v interface{}) []byte { - bytes, err := json.Marshal(v) - if err != nil { - t.Fatal(err) - } - return bytes -} - -type TestClient struct { - mock.Mock -} - -// Request makes a request and return the response data -func (t *TestClient) Request(module string, object zbus.ObjectID, method string, args ...interface{}) (*zbus.Response, error) { - inputs := []interface{}{ - module, object, method, - } - inputs = append(inputs, args...) - - return zbus.NewResponse("", "", t.Called(inputs...)...) -} - -// Stream listens to a stream of events from the server -func (t *TestClient) Stream(ctx context.Context, module string, object zbus.ObjectID, event string) (<-chan zbus.Event, error) { - panic("not implemented") -} - -func TestClientOperation(t *testing.T) { - require := require.New(t) - var client TestClient - - client.On("Request", "module", zbus.ObjectID{}, "test", 123, []string{"hello", "world"}). - Return("result", nil) - - response, err := client.Request("module", zbus.ObjectID{}, "test", 123, []string{"hello", "world"}) - require.NoError(err) - - var str string - var rerr *zbus.RemoteError - require.NoError(response.Unmarshal(0, &str)) - require.NoError(response.Unmarshal(1, &rerr)) - - require.Equal("result", str) - require.Nil(rerr) -} - -type TestOwnerCache struct { - mock.Mock -} - -func (t *TestOwnerCache) OwnerOf(id string) (string, error) { - result := t.Called(id) - return result.String(0), result.Error(1) -} diff --git a/pkg/provision/primitives/volume_test.go b/pkg/provision/primitives/volume_test.go deleted file mode 100644 index cce27907f..000000000 --- a/pkg/provision/primitives/volume_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package primitives - -// func TestVolumeProvisionExists(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) - -// const module = "storage" -// version := zbus.ObjectID{Name: "storage", Version: "0.0.1"} - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: VolumeReservation, -// Data: MustMarshal(t, Volume{}), -// } - -// client.On("Request", module, version, "Path", reservation.ID). -// Return("/some/path", nil) - -// result, err := volumeProvisionImpl(ctx, &reservation) -// require.NoError(err) -// require.EqualValues(VolumeResult{"reservation-id"}, result) -// } - -// func TestVolumeProvisionNew(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) - -// const module = "storage" -// version := zbus.ObjectID{Name: "storage", Version: "0.0.1"} - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: VolumeReservation, -// Data: MustMarshal(t, Volume{ -// Size: 10, -// Type: SSDDiskType, -// }), -// } - -// // force creation by returning an error -// client.On("Request", module, version, "Path", reservation.ID). -// Return("", zbus.RemoteError{"does not exist"}) - -// client.On("Request", module, version, "CreateFilesystem", reservation.ID, 10*gigabyte, pkg.DeviceType(SSDDiskType)). -// Return("/some/path", nil) - -// result, err := volumeProvisionImpl(ctx, &reservation) -// require.NoError(err) -// require.EqualValues(VolumeResult{"reservation-id"}, result) -// } - -// func TestVolumeDecomission(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) - -// const module = "storage" -// version := zbus.ObjectID{Name: "storage", Version: "0.0.1"} - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: VolumeReservation, -// } - -// // force decomission by returning a nil error -// client.On("Request", module, version, "ReleaseFilesystem", reservation.ID). -// Return(nil) - -// err := volumeDecommission(ctx, &reservation) -// require.NoError(err) -// } diff --git a/pkg/provision/primitives/zdb_test.go b/pkg/provision/primitives/zdb_test.go deleted file mode 100644 index f67441ba8..000000000 --- a/pkg/provision/primitives/zdb_test.go +++ /dev/null @@ -1,283 +0,0 @@ -package primitives - -import ( - "github.com/stretchr/testify/mock" - "github.com/threefoldtech/zos/pkg/zdb" -) - -type zdbTestClient struct { - mock.Mock - zdb.Client -} - -func (c *zdbTestClient) Connect() error { - return nil -} - -func (c *zdbTestClient) Close() error { - return nil -} - -func (c *zdbTestClient) CreateNamespace(name string) error { - return c.Called(name).Error(0) -} - -func (c *zdbTestClient) NamespaceSetSize(name string, size uint64) error { - return c.Called(name, size).Error(0) -} - -func (c *zdbTestClient) NamespaceSetPassword(name, password string) error { - return c.Called(name, password).Error(0) -} - -func (c *zdbTestClient) NamespaceSetPublic(name string, public bool) error { - return c.Called(name, public).Error(0) -} - -func (c *zdbTestClient) Exist(name string) (bool, error) { - args := c.Called(name) - return args.Bool(0), args.Error(1) -} - -// func TestZDBProvision(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) - -// passwd := "pa$$w0rd" -// conf := ZDB{ -// Size: 280682, -// Mode: pkg.ZDBModeSeq, -// Password: hex.EncodeToString([]byte(passwd)), -// DiskType: pkg.SSDDevice, -// Public: true, -// } - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: ZDBReservation, -// Data: MustMarshal(t, conf), -// } - -// /* -// Request(string,zbus.ObjectID,string,string,pkg.DeviceType,uint64,pkg.ZDBMode) -// 0: "storage" -// 1: zbus.ObjectID{Name:"storage", Version:"0.0.1"} -// 2: "Allocate" -// 3: "reservation-id" -// 4: "SSD" -// 5: 0x1121a80000000 -// 6: "seq" - -// */ -// client.On("Request", "storage", zbus.ObjectID{Name: "storage", Version: "0.0.1"}, -// "Allocate", "reservation-id", conf.DiskType, conf.Size*gigabyte, conf.Mode). -// Return(pkg.Allocation{ -// VolumeID: "zdb-container-id", -// VolumePath: "/tmp/volume/path", -// }, nil) - -// client.On("Request", "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Inspect", "zdb", pkg.ContainerID("zdb-container-id")). -// Return(pkg.Container{ -// Name: "volume-id", -// Network: pkg.NetworkInfo{ -// Namespace: "net-ns", -// }, -// }, nil) - -// client.On("Request", "identityd", zbus.ObjectID{Name: "manager", Version: "0.0.1"}, -// "Decrypt", []byte(passwd)). -// Return("password", nil) - -// client.On("Request", "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, -// "Addrs", -// "zdb0", "net-ns").Return([]net.IP{net.ParseIP("2001:cdba::3257:9652")}, nil) - -// var zdbClient zdbTestClient -// zdbClient.On("Exist", reservation.ID).Return(false, nil) -// zdbClient.On("CreateNamespace", reservation.ID).Return(nil) -// zdbClient.On("NamespaceSetPassword", reservation.ID, "password").Return(nil) -// zdbClient.On("NamespaceSetPublic", reservation.ID, conf.Public).Return(nil) -// zdbClient.On("NamespaceSetSize", reservation.ID, conf.Size*gigabyte).Return(nil) - -// zdbConnection = func(id pkg.ContainerID) zdb.Client { -// return &zdbClient -// } - -// result, err := zdbProvisionImpl(ctx, &reservation) - -// require.NoError(err) -// require.Equal(reservation.ID, result.Namespace) -// require.Equal("2001:cdba::3257:9652", result.IP) -// require.EqualValues(zdbPort, result.Port) -// } - -// func TestZDBProvisionNoMappingContainerDoesNotExists(t *testing.T) { -// require := require.New(t) - -// var client TestClient -// ctx := context.Background() -// ctx = WithZBus(ctx, &client) - -// passwd := "pa$$w0rd" -// zdb := ZDB{ -// Size: 10, -// Mode: pkg.ZDBModeSeq, -// Password: hex.EncodeToString([]byte(passwd)), -// DiskType: pkg.SSDDevice, -// Public: true, -// } - -// reservation := Reservation{ -// ID: "reservation-id", -// User: "user", -// Type: ZDBReservation, -// Data: MustMarshal(t, zdb), -// } - -// // return false on cache force creation - -// // it's followed by allocation request to see if there is already a -// // zdb instance running that has enough space for the ns - -// client.On("Request", "identityd", zbus.ObjectID{Name: "manager", Version: "0.0.1"}, -// "Decrypt", []byte(passwd)). -// Return("password", nil) - -// client.On("Request", "storage", zbus.ObjectID{Name: "storage", Version: "0.0.1"}, -// "Allocate", -// reservation.ID, zdb.DiskType, zdb.Size*gigabyte, zdb.Mode, -// ).Return(pkg.Allocation{ -// VolumeID: "container-id", -// VolumePath: "/path/to/volume", -// }, nil) - -// // container does NOT exists, so inspect still should return an error -// client.On("Request", "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Inspect", "zdb", pkg.ContainerID("container-id")). -// Return(pkg.Container{}, fmt.Errorf("not found")) - -// client.On("Request", "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, -// "Addrs", -// "zdb0", "net-ns").Return([]net.IP{net.ParseIP("2001:cdba::3257:9652")}, nil) - -// client.On("Request", "flist", zbus.ObjectID{Name: "flist", Version: "0.0.1"}, -// "Mount", -// "https://hub.grid.tf/tf-autobuilder/threefoldtech-0-db-development.flist", -// "", pkg.MountOptions{ -// Limit: 10, -// ReadOnly: false, -// }).Return("/path/to/volume", nil) - -// // The hw address in this mock call, is based on the VolumeID above. changing the VolumeID -// // will require this HW address to change -// client.On("Request", "network", zbus.ObjectID{Name: "network", Version: "0.0.1"}, -// "ZDBPrepare", []byte{0x7e, 0xcc, 0xc3, 0x81, 0xa2, 0x2e}).Return("net-ns", nil) - -// client.On("Request", "container", zbus.ObjectID{Name: "container", Version: "0.0.1"}, -// "Run", -// "zdb", -// pkg.Container{ -// Name: "container-id", -// RootFS: "/path/to/volume", -// Network: pkg.NetworkInfo{Namespace: "net-ns"}, -// Mounts: []pkg.MountInfo{ -// pkg.MountInfo{ -// Source: "/path/to/volume", -// Target: "/data", -// }, -// pkg.MountInfo{ -// Source: "/var/run/zdb_container-id", -// Target: "/socket", -// }, -// }, -// Entrypoint: "/bin/zdb --data /data --index /data --mode seq --listen :: --port 9900 --socket /socket/zdb.sock --dualnet", -// Interactive: false, -// }, -// ).Return(pkg.ContainerID("container-id"), nil) - -// _, err := zdbProvisionImpl(ctx, &reservation) - -// // unfortunately the provision will try (as last step) to dial -// // the unix socket of the server, and then actually configure -// // the namespace and set the limits, password, etc... -// // for now we just handle the connection error and assume -// // it succeeded. -// // TODO: start a mock server on this address -// require.Error(err) -// require.True(strings.Contains(err.Error(), "/var/run/zdb_container-id")) -// } - -// func Test_findDataVolume(t *testing.T) { -// type args struct { -// mounts []pkg.MountInfo -// } -// tests := []struct { -// name string -// args args -// want string -// wantErr bool -// }{ -// { -// name: "exists", -// args: args{ -// mounts: []pkg.MountInfo{ -// { -// Target: "/data", -// Source: "/mnt/sda/subovl1", -// }, -// { -// Target: "/etc/resolv.conf", -// Source: "/etc/resolv.conf", -// }, -// }, -// }, -// want: "subovl1", -// wantErr: false, -// }, -// { -// name: "non-exists", -// args: args{ -// mounts: []pkg.MountInfo{ -// { -// Target: "/etc/resolv.conf", -// Source: "/etc/resolv.conf", -// }, -// }, -// }, -// want: "", -// wantErr: true, -// }, -// { -// name: "empty", -// args: args{ -// mounts: []pkg.MountInfo{}, -// }, -// want: "", -// wantErr: true, -// }, -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// got, err := findDataVolume(tt.args.mounts) -// if tt.wantErr { -// require.Error(t, err) -// } else { -// assert.Equal(t, tt.want, got) -// } -// }) -// } -// } - -// func findDataVolume(mounts []pkg.MountInfo) (string, error) { -// for _, mount := range mounts { -// if mount.Target == "/data" { -// return filepath.Base(mount.Source), nil -// } -// } -// return "", fmt.Errorf("data volume not found") -// } From e50646c7982f9bbcb28a7f135a4c521f907cdfaa Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Mon, 20 Apr 2020 14:03:00 +0200 Subject: [PATCH 08/14] move tfarmer to https://github.com/threefoldtech/tfexplorer --- pkg/identity/keys.go | 14 ++--- pkg/identity/userid.go | 109 -------------------------------------- scripts/collect.sh | 3 +- tools/tffarmer/farm.go | 107 ------------------------------------- tools/tffarmer/network.go | 83 ----------------------------- 5 files changed, 8 insertions(+), 308 deletions(-) delete mode 100644 pkg/identity/userid.go delete mode 100644 tools/tffarmer/farm.go delete mode 100644 tools/tffarmer/network.go diff --git a/pkg/identity/keys.go b/pkg/identity/keys.go index 49a5ffd31..56154239e 100644 --- a/pkg/identity/keys.go +++ b/pkg/identity/keys.go @@ -15,11 +15,11 @@ import ( var ( // SeedVersion1 (binary seed) - seedVersion1 = versioned.MustParse("1.0.0") + SeedVersion1 = versioned.MustParse("1.0.0") // SeedVersion11 (json mnemonic) - seedVersion11 = versioned.MustParse("1.1.0") + SeedVersion11 = versioned.MustParse("1.1.0") // SeedVersionLatest link to latest seed version - seedVersionLatest = seedVersion11 + SeedVersionLatest = SeedVersion11 ) // KeyPair holds a public and private side of an ed25519 key pair @@ -50,7 +50,7 @@ func GenerateKeyPair() (k KeyPair, err error) { func (k *KeyPair) Save(path string) error { seed := k.PrivateKey.Seed() - return versioned.WriteFile(path, seedVersion1, seed, 0400) + return versioned.WriteFile(path, SeedVersion1, seed, 0400) } // LoadSeed from path @@ -59,13 +59,13 @@ func LoadSeed(path string) ([]byte, error) { if versioned.IsNotVersioned(err) { // this is a compatibility code for seed files // in case it does not have any version information - versioned.WriteFile(path, seedVersionLatest, seed, 0400) - version = seedVersionLatest + versioned.WriteFile(path, SeedVersionLatest, seed, 0400) + version = SeedVersionLatest } else if err != nil { return nil, err } - if version.NE(seedVersion1) { + if version.NE(SeedVersion1) { return nil, fmt.Errorf("unknown seed version") } diff --git a/pkg/identity/userid.go b/pkg/identity/userid.go deleted file mode 100644 index 87f98eedc..000000000 --- a/pkg/identity/userid.go +++ /dev/null @@ -1,109 +0,0 @@ -package identity - -import ( - "encoding/json" - "fmt" - - "github.com/rs/zerolog/log" - "github.com/tyler-smith/go-bip39" - "golang.org/x/crypto/ed25519" - - "github.com/threefoldtech/zos/pkg/versioned" -) - -// UserIdentity defines serializable struct to identify a user -type UserIdentity struct { - // Mnemonic words of Private Key - Mnemonic string `json:"mnemonic"` - // ThreebotID generated by explorer - ThreebotID uint64 `json:"threebotid"` - // Internal keypair not exported - key KeyPair -} - -// NewUserIdentity create a new UserIdentity from existing key -func NewUserIdentity(key KeyPair, threebotid uint64) *UserIdentity { - return &UserIdentity{ - key: key, - ThreebotID: threebotid, - } -} - -// Key returns the internal KeyPair -func (u *UserIdentity) Key() KeyPair { - return u.key -} - -// Load fetch a seed file and initialize key based on mnemonic -func (u *UserIdentity) Load(path string) error { - version, buf, err := versioned.ReadFile(path) - if err != nil { - return err - } - - if version.Compare(seedVersion1) == 0 { - return fmt.Errorf("seed file too old, please update it using 'tfuser id convert' command") - } - - if version.NE(seedVersionLatest) { - return fmt.Errorf("unsupported seed version") - } - - err = json.Unmarshal(buf, &u) - if err != nil { - return err - } - - return u.FromMnemonic(u.Mnemonic) -} - -// FromMnemonic initialize the Key (KeyPair) from mnemonic argument -func (u *UserIdentity) FromMnemonic(mnemonic string) error { - seed, err := bip39.EntropyFromMnemonic(mnemonic) - if err != nil { - return err - } - - // Loading mnemonic - u.key, err = FromSeed(seed) - if err != nil { - return err - } - - return nil -} - -// Save dumps UserIdentity into a versioned file -func (u *UserIdentity) Save(path string) error { - var err error - - log.Info().Msg("generating seed mnemonic") - - // Generate mnemonic of private key - u.Mnemonic, err = bip39.NewMnemonic(u.key.PrivateKey.Seed()) - if err != nil { - return err - } - - // Versioning json output - buf, err := json.Marshal(u) - if err != nil { - return err - } - - // Saving json to file - log.Info().Str("filename", path).Msg("writing user identity") - versioned.WriteFile(path, seedVersion11, buf, 0400) - - return nil -} - -// PrivateKey implements the client.Identity interface -func (u *UserIdentity) PrivateKey() ed25519.PrivateKey { - return u.Key().PrivateKey -} - -// Identity implements the Identifier interface -func (u *UserIdentity) Identity() string { - return fmt.Sprintf("%d", u.ThreebotID) -} diff --git a/scripts/collect.sh b/scripts/collect.sh index 17dc0af2d..3eb09889b 100755 --- a/scripts/collect.sh +++ b/scripts/collect.sh @@ -12,5 +12,4 @@ if [ -z "${archive}" ]; then fi mkdir -p ${archive}/bin ${archive}/etc -cp bin/* ${archive}/bin/ -cp -r etc/* ${archive}/etc/ \ No newline at end of file +cp tfexplorer ${archive}/bin/ \ No newline at end of file diff --git a/tools/tffarmer/farm.go b/tools/tffarmer/farm.go deleted file mode 100644 index 16dcc7330..000000000 --- a/tools/tffarmer/farm.go +++ /dev/null @@ -1,107 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/threefoldtech/tfexplorer/models/generated/directory" - "github.com/threefoldtech/tfexplorer/schema" - "github.com/urfave/cli" -) - -func registerFarm(c *cli.Context) (err error) { - name := c.Args().First() - if name == "" { - return fmt.Errorf("farm name needs to be specified") - } - - addrs := c.StringSlice("addresses") - email := c.String("email") - iyo := c.String("iyo_organization") - - addresses := make([]directory.WalletAddress, len(addrs)) - for i := range addrs { - addresses[i].Asset, addresses[i].Address, err = splitAddressCode(addrs[i]) - if err != nil { - return err - } - } - - farm := directory.Farm{ - Name: name, - ThreebotId: int64(userid.ThreebotID), - Email: schema.Email(email), - IyoOrganization: iyo, - WalletAddresses: addresses, - } - - farm.ID, err = db.FarmRegister(farm) - if err != nil { - return err - } - - fmt.Println("Farm updated successfully") - fmt.Println(formatFarm(farm)) - return nil -} - -func updateFarm(c *cli.Context) error { - id := c.Int64("id") - farm, err := db.FarmGet(schema.ID(id)) - if err != nil { - return err - } - - addrs := c.StringSlice("addresses") - email := c.String("email") - iyo := c.String("iyo_organization") - - if len(addrs) > 0 { - addresses := make([]directory.WalletAddress, len(addrs)) - for i := range addrs { - addresses[i].Asset, addresses[i].Address, err = splitAddressCode(addrs[i]) - if err != nil { - return err - } - } - farm.WalletAddresses = addresses - } - - if email != "" { - farm.Email = schema.Email(email) - } - - if iyo != "" { - farm.IyoOrganization = iyo - } - - if err := db.FarmUpdate(farm); err != nil { - return err - } - - fmt.Println("Farm registered successfully") - fmt.Println(formatFarm(farm)) - return nil -} - -func splitAddressCode(addr string) (string, string, error) { - ss := strings.Split(addr, ":") - if len(ss) != 2 { - return "", "", fmt.Errorf("wrong format for wallet address %s, should be 'asset:address'", addr) - } - - return ss[0], ss[1], nil -} -func formatFarm(farm directory.Farm) string { - b := &strings.Builder{} - fmt.Fprintf(b, "ID: %d\n", farm.ID) - fmt.Fprintf(b, "Name: %s\ns", farm.Name) - fmt.Fprintf(b, "Email: %s\n", farm.Email) - fmt.Fprintf(b, "Farmer TheebotID: %d\n", farm.ThreebotId) - fmt.Fprintf(b, "IYO organization: %s\n", farm.IyoOrganization) - fmt.Fprintf(b, "Wallet addresses:\n") - for _, a := range farm.WalletAddresses { - fmt.Fprintf(b, "%s:%s\n", a.Asset, a.Address) - } - return b.String() -} diff --git a/tools/tffarmer/network.go b/tools/tffarmer/network.go deleted file mode 100644 index 067ddb345..000000000 --- a/tools/tffarmer/network.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "fmt" - "net" - - "github.com/threefoldtech/tfexplorer/models/generated/directory" - "github.com/threefoldtech/tfexplorer/schema" - - "github.com/urfave/cli" -) - -func configPublic(c *cli.Context) error { - var ( - iface = c.String("iface") - ) - - var nv4 *net.IPNet - var nv6 *net.IPNet - var gw4 net.IP - var gw6 net.IP - - for _, ip := range c.StringSlice("ip") { - i, ipnet, err := net.ParseCIDR(ip) - if err != nil { - return fmt.Errorf("invalid cidr(%s): %s", ip, err) - } - - ipnet.IP = i - if ipnet.IP.To4() == nil { - //ipv6 - if nv6 != nil { - return fmt.Errorf("only one ipv6 range is supported") - } - nv6 = ipnet - } else { - //ipv4 - if nv4 != nil { - return fmt.Errorf("only one ipv4 range is supported") - } - nv4 = ipnet - } - } - - for _, s := range c.StringSlice("gw") { - gw := net.ParseIP(s) - if gw == nil { - return fmt.Errorf("invalid gw '%s'", s) - } - if gw.To4() == nil { - //ipv6 - if gw6 != nil { - return fmt.Errorf("only one gw ipv6 is supported") - } - gw6 = gw - } else { - //ipv4 - if gw4 != nil { - return fmt.Errorf("only one gw ipv4 is supported") - } - gw4 = gw - } - } - - node := c.Args().First() - pubIface := directory.PublicIface{ - Master: iface, - Gw4: gw4, - Gw6: gw6, - } - if nv4 != nil { - pubIface.Ipv4 = schema.IPRange{IPNet: *nv4} - } - if nv6 != nil { - pubIface.Ipv6 = schema.IPRange{IPNet: *nv6} - } - - if err := db.NodeSetPublic(node, pubIface); err != nil { - return err - } - fmt.Printf("public interface configured on node %s\n", node) - return nil -} From be48805b3636c742941e1435761838fa3ff612d4 Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Tue, 21 Apr 2020 10:50:31 +0200 Subject: [PATCH 09/14] don't forget to call statser method after provision/decommission --- pkg/provision/engine.go | 25 ++++++++++++++++++++++- pkg/provision/explorer/source.go | 2 -- pkg/provision/primitives/counters.go | 30 ++++++++++++++-------------- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go index 8ca0610de..a9e7adefe 100644 --- a/pkg/provision/engine.go +++ b/pkg/provision/engine.go @@ -64,6 +64,7 @@ func New(opts EngineOps) *Engine { source: opts.Source, cache: opts.Cache, feedback: opts.Feedback, + provisioners: opts.Provisioners, decomissioners: opts.Decomissioners, signer: opts.Signer, statser: opts.Statser, @@ -166,6 +167,10 @@ func (e *Engine) provision(ctx context.Context, r *Reservation) error { return errors.Wrapf(err, "failed to cache reservation %s locally", r.ID) } + if err := e.statser.Increment(r); err != nil { + log.Err(err).Str("reservation_id", r.ID).Msg("failed to increment workloads statistics") + } + return nil } @@ -197,6 +202,10 @@ func (e *Engine) decommission(ctx context.Context, r *Reservation) error { return errors.Wrapf(err, "failed to remove reservation %s from cache", r.ID) } + if err := e.statser.Decrement(r); err != nil { + log.Err(err).Str("reservation_id", r.ID).Msg("failed to decrement workloads statistics") + } + if err := e.feedback.Deleted(e.nodeID, r.ID); err != nil { return errors.Wrap(err, "failed to mark reservation as deleted") } @@ -243,7 +252,21 @@ func (e *Engine) updateStats() error { wl := e.statser.CurrentWorkloads() r := e.statser.CurrentUnits() - log.Info().Msgf("reserved resource %+v", r) + log.Info(). + Uint16("network", wl.Network). + Uint16("volume", wl.Volume). + Uint16("zDBNamespace", wl.ZDBNamespace). + Uint16("container", wl.Container). + Uint16("k8sVM", wl.K8sVM). + Uint16("proxy", wl.Proxy). + Uint16("reverseProxy", wl.ReverseProxy). + Uint16("subdomain", wl.Subdomain). + Uint16("delegateDomain", wl.DelegateDomain). + Uint64("cru", r.Cru). + Float64("mru", r.Mru). + Float64("hru", r.Hru). + Float64("sru", r.Sru). + Msgf("provision statistics") log.Info().Msgf("provisionned workloads %+v", wl) return e.feedback.UpdateStats(e.nodeID, wl, r) diff --git a/pkg/provision/explorer/source.go b/pkg/provision/explorer/source.go index fcb5855f5..48708f4b4 100644 --- a/pkg/provision/explorer/source.go +++ b/pkg/provision/explorer/source.go @@ -4,7 +4,6 @@ import ( "fmt" "sort" - "github.com/rs/zerolog/log" "github.com/threefoldtech/tfexplorer/client" "github.com/threefoldtech/zos/pkg" "github.com/threefoldtech/zos/pkg/provision" @@ -40,7 +39,6 @@ func (r *Poller) Poll(nodeID pkg.Identifier, from uint64) ([]*provision.Reservat result := make([]*provision.Reservation, 0, len(list)) for _, wl := range list { - log.Info().Msgf("convert %+v", wl) r, err := r.inputConv(wl) if err != nil { return nil, err diff --git a/pkg/provision/primitives/counters.go b/pkg/provision/primitives/counters.go index 3fbc6c921..410827d31 100644 --- a/pkg/provision/primitives/counters.go +++ b/pkg/provision/primitives/counters.go @@ -19,37 +19,37 @@ type Counter interface { Current() uint64 } -// counterImpl value for safe increment/decrement -type counterImpl uint64 +// CounterUint64 value for safe increment/decrement +type CounterUint64 uint64 // Increment counter atomically by one -func (c *counterImpl) Increment(v uint64) uint64 { +func (c *CounterUint64) Increment(v uint64) uint64 { return atomic.AddUint64((*uint64)(c), v) } // Decrement counter atomically by one -func (c *counterImpl) Decrement(v uint64) uint64 { +func (c *CounterUint64) Decrement(v uint64) uint64 { return atomic.AddUint64((*uint64)(c), -v) } // Current returns the current value -func (c *counterImpl) Current() uint64 { +func (c *CounterUint64) Current() uint64 { return atomic.LoadUint64((*uint64)(c)) } // Counters tracks the amount of primitives workload deployed and // the amount of resource unit used type Counters struct { - containers counterImpl - volumes counterImpl - networks counterImpl - zdbs counterImpl - vms counterImpl - - SRU counterImpl // SSD storage in bytes - HRU counterImpl // HDD storage in bytes - MRU counterImpl // Memory storage in bytes - CRU counterImpl // CPU count absolute + containers CounterUint64 + volumes CounterUint64 + networks CounterUint64 + zdbs CounterUint64 + vms CounterUint64 + + SRU CounterUint64 // SSD storage in bytes + HRU CounterUint64 // HDD storage in bytes + MRU CounterUint64 // Memory storage in bytes + CRU CounterUint64 // CPU count absolute } // CurrentWorkloads return the number of each workloads provisioned on the system From 9c52059166a5a52886815691541328533f95cfe0 Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Wed, 22 Apr 2020 15:23:33 +0200 Subject: [PATCH 10/14] add missing call to statser.Increment in provision engine --- go.mod | 1 - go.sum | 5 +---- pkg/provision/engine.go | 1 - pkg/provision/primitives/counters.go | 3 +++ pkg/provision/source.go | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 2347214a1..c2633eecb 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,6 @@ require ( github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae github.com/threefoldtech/tfexplorer v0.0.0-00010101000000-000000000000 github.com/threefoldtech/zbus v0.1.3 - github.com/tyler-smith/go-bip39 v1.0.2 github.com/urfave/cli v1.22.3 github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df diff --git a/go.sum b/go.sum index c29855a8a..36c61fb8a 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,6 @@ code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48/go.mod h1:wN/zk firebase.google.com/go v3.12.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw= -github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U= github.com/Masterminds/squirrel v0.0.0-20161115235646-20f192218cf5/go.mod h1:xnKTFzjGUiZtiOagBsfnvomW+nJg2usB1ZpordQWqNM= github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= @@ -544,10 +542,9 @@ github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae h1:vgGSvdW5Lqg+I1 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae/go.mod h1:quDq6Se6jlGwiIKia/itDZxqC5rj6/8OdFyMMAwTxCs= github.com/threefoldtech/zbus v0.1.3 h1:18DnIzximRbATle5ZdZz0i84n/bCYB8k/gkhr2dXayc= github.com/threefoldtech/zbus v0.1.3/go.mod h1:ZtiRpcqzEBJetVQDsEbw0p48h/AF3O1kf0tvd30I0BU= -github.com/threefoldtech/zos v0.2.3/go.mod h1:Fs/ckf/CkysugXBAXf5+zPxStH55FyJZWHJOJV4T1Sk= +github.com/threefoldtech/zos v0.2.4-rc2/go.mod h1:7A2oflcmSVsHFC4slOcydWgJyFBMFMH9wsaTRv+CnTA= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tyler-smith/go-bip32 v0.0.0-20170922074101-2c9cfd177564/go.mod h1:0/YuQQF676+d4CMNclTqGUam1EDwz0B8o03K9pQqA3c= github.com/tyler-smith/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= diff --git a/pkg/provision/engine.go b/pkg/provision/engine.go index a9e7adefe..2197a44ab 100644 --- a/pkg/provision/engine.go +++ b/pkg/provision/engine.go @@ -267,7 +267,6 @@ func (e *Engine) updateStats() error { Float64("hru", r.Hru). Float64("sru", r.Sru). Msgf("provision statistics") - log.Info().Msgf("provisionned workloads %+v", wl) return e.feedback.UpdateStats(e.nodeID, wl, r) } diff --git a/pkg/provision/primitives/counters.go b/pkg/provision/primitives/counters.go index 410827d31..0cabf5b36 100644 --- a/pkg/provision/primitives/counters.go +++ b/pkg/provision/primitives/counters.go @@ -88,10 +88,13 @@ func (c *Counters) Increment(r *provision.Reservation) error { switch r.Type { case VolumeReservation: + c.volumes.Increment(1) u, err = processVolume(r) case ContainerReservation: + c.containers.Increment(1) u, err = processContainer(r) case ZDBReservation: + c.zdbs.Increment(1) u, err = processZdb(r) case KubernetesReservation: c.vms.Increment(1) diff --git a/pkg/provision/source.go b/pkg/provision/source.go index f1672f56d..e86ec57a7 100644 --- a/pkg/provision/source.go +++ b/pkg/provision/source.go @@ -133,7 +133,7 @@ func (s *decommissionSource) Reservations(ctx context.Context) <-chan *Reservati for { <-time.After(time.Second * 20) //TODO: make configuration ? default value ? - log.Debug().Msg("check for expired reservation") + log.Info().Msg("check for expired reservation") reservations, err := s.store.GetExpired() if err != nil { From 909f5086e80c51630226be7680683a7af79ec937 Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Wed, 22 Apr 2020 18:05:30 +0200 Subject: [PATCH 11/14] uncomment parked code --- cmds/capacityd/main.go | 48 +++++++++++++++++++++--------------------- pkg/app/boot.go | 10 ++++----- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/cmds/capacityd/main.go b/cmds/capacityd/main.go index 1d39999e8..354732421 100644 --- a/cmds/capacityd/main.go +++ b/cmds/capacityd/main.go @@ -8,6 +8,7 @@ import ( "github.com/cenkalti/backoff/v3" "github.com/threefoldtech/tfexplorer/client" + "github.com/threefoldtech/tfexplorer/models/generated/directory" "github.com/threefoldtech/zos/pkg/app" "github.com/threefoldtech/zos/pkg/capacity" "github.com/threefoldtech/zos/pkg/monitord" @@ -31,7 +32,7 @@ func cap(ctx context.Context, client zbus.Client) { } // call this now so we block here until identityd is ready to serve us - // nodeID := identity.NodeID().Identity() + nodeID := identity.NodeID().Identity() r := capacity.NewResourceOracle(storage) @@ -48,32 +49,31 @@ func cap(ctx context.Context, client zbus.Client) { Msg("resource units found") log.Info().Msg("read DMI info") - // dmi, err := r.DMI() - // if err != nil { - // log.Fatal().Err(err).Msgf("failed to read DMI information from hardware") - // } - - // disks, err := r.Disks() - // if err != nil { - // log.Fatal().Err(err).Msgf("failed to read smartctl information from disks") - // } - - // hypervisor, err := r.GetHypervisor() - // if err != nil { - // log.Fatal().Err(err).Msgf("failed to read virtualized state") - // } - - // ru := directory.ResourceAmount{ - // Cru: resources.CRU, - // Mru: float64(resources.MRU), - // Hru: float64(resources.HRU), - // Sru: float64(resources.SRU), - // } + dmi, err := r.DMI() + if err != nil { + log.Fatal().Err(err).Msgf("failed to read DMI information from hardware") + } + + disks, err := r.Disks() + if err != nil { + log.Fatal().Err(err).Msgf("failed to read smartctl information from disks") + } + + hypervisor, err := r.GetHypervisor() + if err != nil { + log.Fatal().Err(err).Msgf("failed to read virtualized state") + } + + ru := directory.ResourceAmount{ + Cru: resources.CRU, + Mru: float64(resources.MRU), + Hru: float64(resources.HRU), + Sru: float64(resources.SRU), + } setCapacity := func() error { log.Info().Msg("sends capacity detail to BCDB") - // return cl.NodeSetCapacity(nodeID, ru, *dmi, disks, hypervisor) - return nil + return cl.NodeSetCapacity(nodeID, ru, *dmi, disks, hypervisor) } bo := backoff.NewExponentialBackOff() bo.MaxElapsedTime = 0 // retry forever diff --git a/pkg/app/boot.go b/pkg/app/boot.go index 4900deb4c..f7d41603b 100644 --- a/pkg/app/boot.go +++ b/pkg/app/boot.go @@ -5,24 +5,24 @@ import ( "path/filepath" ) -// BootedPath is the path where to store the booted flag -var BootedPath = "/var/run/modules" +// bootedPath is the path where to store the booted flag +var bootedPath = "/var/run/modules" // MarkBooted creates a file in a memory // this file then can be used to check if "something" has been restared // if its the first time it starts func MarkBooted(name string) error { - if err := os.MkdirAll(BootedPath, 0770); err != nil { + if err := os.MkdirAll(bootedPath, 0770); err != nil { return err } - path := filepath.Join(BootedPath, name) + path := filepath.Join(bootedPath, name) _, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE|os.O_TRUNC|os.O_EXCL, 0666) return err } // IsFirstBoot checks if the a file has been created by MarkBooted function func IsFirstBoot(name string) bool { - path := filepath.Join(BootedPath, name) + path := filepath.Join(bootedPath, name) _, err := os.Stat(path) return !(err == nil) } From 2a17449afc435f34032b32cc87deb3875d13a1cd Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Wed, 22 Apr 2020 19:03:39 +0200 Subject: [PATCH 12/14] set version of tfexplorer to 0.2.5 --- go.mod | 6 +----- go.sum | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index c2633eecb..4a5f40600 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/stellar/go v0.0.0-20200325172527-9cabbc6b9388 github.com/stretchr/testify v1.5.1 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae - github.com/threefoldtech/tfexplorer v0.0.0-00010101000000-000000000000 + github.com/threefoldtech/tfexplorer v0.2.5 github.com/threefoldtech/zbus v0.1.3 github.com/urfave/cli v1.22.3 github.com/vishvananda/netlink v1.0.0 @@ -48,7 +48,3 @@ require ( ) replace github.com/docker/distribution v2.7.1+incompatible => github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible - -replace github.com/threefoldtech/tfgateway => ../tf_gateway - -replace github.com/threefoldtech/tfexplorer => ../tfexplorer diff --git a/go.sum b/go.sum index 36c61fb8a..8c29762f4 100644 --- a/go.sum +++ b/go.sum @@ -540,6 +540,8 @@ github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae h1:vgGSvdW5Lqg+I1aZOlG32uyE6xHpLdKhZzcTEktz5wM= github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae/go.mod h1:quDq6Se6jlGwiIKia/itDZxqC5rj6/8OdFyMMAwTxCs= +github.com/threefoldtech/tfexplorer v0.2.5 h1:xFDSqyzL8eOMpdS+T/LFFZZciI+vNZ5oZKKch8WNFME= +github.com/threefoldtech/tfexplorer v0.2.5/go.mod h1:TugylEDgMNKk4ZIzee9PpVmIcLNLaRLdiXuhHTt/AR0= github.com/threefoldtech/zbus v0.1.3 h1:18DnIzximRbATle5ZdZz0i84n/bCYB8k/gkhr2dXayc= github.com/threefoldtech/zbus v0.1.3/go.mod h1:ZtiRpcqzEBJetVQDsEbw0p48h/AF3O1kf0tvd30I0BU= github.com/threefoldtech/zos v0.2.4-rc2/go.mod h1:7A2oflcmSVsHFC4slOcydWgJyFBMFMH9wsaTRv+CnTA= From 96dff225166a06a2843dc3eee1e64ec0a186baee Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Wed, 22 Apr 2020 19:10:37 +0200 Subject: [PATCH 13/14] revert flist binary collection script --- scripts/collect.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/collect.sh b/scripts/collect.sh index 3eb09889b..17dc0af2d 100755 --- a/scripts/collect.sh +++ b/scripts/collect.sh @@ -12,4 +12,5 @@ if [ -z "${archive}" ]; then fi mkdir -p ${archive}/bin ${archive}/etc -cp tfexplorer ${archive}/bin/ \ No newline at end of file +cp bin/* ${archive}/bin/ +cp -r etc/* ${archive}/etc/ \ No newline at end of file From 42405ac657d0ce81ff5647f8f24b5dbf3590e32f Mon Sep 17 00:00:00 2001 From: Christophe de Carvalho Date: Thu, 23 Apr 2020 10:35:36 +0200 Subject: [PATCH 14/14] remove coverage tracking --- .github/workflows/test.yaml | 10 +--------- pkg/Makefile | 13 ++++--------- pkg/buildscripts/go-coverage.sh | 13 ------------- 3 files changed, 5 insertions(+), 31 deletions(-) delete mode 100644 pkg/buildscripts/go-coverage.sh diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 71874fce8..4c1d6f25d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -25,18 +25,10 @@ jobs: - name: Run tests run: | cd pkg - make coverage + make testrace env: GO111MODULE: on - - name: Send coverage - if: success() - run: | - cd pkg - bash <(curl -s https://codecov.io/bash) - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - - name: Build binaries run: | cd cmds diff --git a/pkg/Makefile b/pkg/Makefile index 94113338a..467a7ddc8 100644 --- a/pkg/Makefile +++ b/pkg/Makefile @@ -1,8 +1,5 @@ PWD := $(shell pwd) GOPATH := $(shell go env GOPATH) -# LDFLAGS := $(shell go run buildscripts/gen-ldflags.go) - -# BUILD_LDFLAGS := '$(LDFLAGS)' all: build @@ -45,7 +42,6 @@ spelling: static: go run honnef.co/go/tools/cmd/staticcheck -- ./... -# Builds minio, runs the verifiers then runs the tests. check: test test: verifiers build # we already ran vet separately, so safe to turn it off here @@ -55,12 +51,11 @@ test: verifiers build done testrace: verifiers build - @echo "Running unit tests with GOFLAGS=${GOFLAGS}" # we already ran vet separately, so safe to turn it off here - @CGO_ENABLED=1 go test -v -vet=off -race ./... - -coverage: verifiers build - @(env bash $(PWD)/buildscripts/go-coverage.sh) + @echo "Running unit tests with GOFLAGS=${GOFLAGS}" + for pkg in $(shell go list ./... | grep -Ev "stubs|network" ); do \ + go test -v -vet=off -race $$pkg; \ + done generate: @echo "Generating modules client stubs" diff --git a/pkg/buildscripts/go-coverage.sh b/pkg/buildscripts/go-coverage.sh deleted file mode 100644 index 479f1bb68..000000000 --- a/pkg/buildscripts/go-coverage.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -e -echo "" > coverage.txt - -for d in $(go list ./... | grep -Ev "stubs|network" ); do - echo "test $d" - go test -vet=off -coverprofile=profile.out -race -covermode=atomic "$d" - if [ -f profile.out ]; then - cat profile.out >> coverage.txt - rm profile.out - fi -done \ No newline at end of file