From f91a31ecfbe0c45990ae49ab087c77c47dd4e6df Mon Sep 17 00:00:00 2001 From: Yuji Ito Date: Sat, 11 Feb 2023 19:10:06 +0900 Subject: [PATCH] wip Signed-off-by: Yuji Ito --- .gitignore | 2 + Makefile | 11 +- agent/core/driver.go | 8 - agent/core/pod.go | 58 ------- agent/core/system.go | 241 -------------------------- agent/core/types.go | 38 ---- agent/cri/driver.go | 25 +-- agent/global/driver.go | 32 ---- agent/global/handler.go | 34 ---- agent/global/types.go | 5 - agent/local/driver.go | 16 -- agent/local/handler.go | 74 -------- agent/local/types.go | 1 - agent/resource/account/kvs.go | 121 +++++++++++++ agent/resource/account/manager.go | 35 ++++ agent/resource/local_datastore.go | 57 ++++++ agent/resource/manager.go | 68 ++++++++ agent/resource/node/manager.go | 19 ++ agent/resource/pod/command_handler.go | 71 ++++++++ agent/resource/pod/kvs.go | 31 ++++ agent/resource/pod/manager.go | 186 ++++++++++++++++++++ agent/resource/pod/messaging.go | 65 +++++++ agent/system/command_driver.go | 33 ++++ agent/system/command_handler.go | 39 +++++ agent/system/system.go | 74 ++++++++ api/account.go | 10 ++ api/meta.go | 17 ++ api/pod.go | 25 +++ cmd/agent/main.go | 203 ++++++++++------------ cmd/agent/system_event_handler.go | 21 --- cmd/sample/main.go | 15 ++ dist/sample.app.json | 13 ++ dist/sample.container.json | 4 + {dist => src}/404.html | 2 +- src/command.ts | 110 ++++++++++-- {dist => src}/error.html | 0 src/index.html | 3 +- src/index.ts | 52 +++--- 38 files changed, 1125 insertions(+), 694 deletions(-) delete mode 100644 agent/core/driver.go delete mode 100644 agent/core/pod.go delete mode 100644 agent/core/system.go delete mode 100644 agent/core/types.go delete mode 100644 agent/global/driver.go delete mode 100644 agent/global/handler.go delete mode 100644 agent/global/types.go delete mode 100644 agent/local/driver.go delete mode 100644 agent/local/handler.go delete mode 100644 agent/local/types.go create mode 100644 agent/resource/account/kvs.go create mode 100644 agent/resource/account/manager.go create mode 100644 agent/resource/local_datastore.go create mode 100644 agent/resource/manager.go create mode 100644 agent/resource/node/manager.go create mode 100644 agent/resource/pod/command_handler.go create mode 100644 agent/resource/pod/kvs.go create mode 100644 agent/resource/pod/manager.go create mode 100644 agent/resource/pod/messaging.go create mode 100644 agent/system/command_driver.go create mode 100644 agent/system/command_handler.go create mode 100644 agent/system/system.go create mode 100644 api/account.go create mode 100644 api/meta.go create mode 100644 api/pod.go delete mode 100644 cmd/agent/system_event_handler.go create mode 100644 cmd/sample/main.go create mode 100644 dist/sample.app.json create mode 100644 dist/sample.container.json rename {dist => src}/404.html (97%) rename {dist => src}/error.html (100%) diff --git a/.gitignore b/.gitignore index ff1a20d..800a987 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,13 @@ /build/ /coverage /dist/*.wasm +/dist/404.html /dist/colonio_go.js /dist/colonio.js /dist/colonio.wasm /dist/container.js /dist/controller.js +/dist/error.html /dist/index.html /dist/index.js /dist/test.js diff --git a/Makefile b/Makefile index 64a3ce5..64a6721 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ COLONIO_BRANCH := main COLONIO_FILES := dist/colonio.js dist/colonio_go.js dist/colonio.wasm GO_FILES := $(shell find . -name *.go) -OINARI_FILES := dist/wasm_exec.js +OINARI_FILES := dist/wasm_exec.js dist/404.html dist/error.html dist/index.html run: build while true; do ./bin/seed --debug -p 8080; done @@ -19,8 +19,9 @@ setup: $(MAKE) -C build/colonio build BUILD_TYPE=Debug npm install -build: $(COLONIO_FILES) $(GO_FILES) $(OINARI_FILES) bin/seed dist/index.html src/keys.ts src/colonio.d.ts src/colonio_go.d.ts +build: $(COLONIO_FILES) $(GO_FILES) $(OINARI_FILES) bin/seed src/colonio.d.ts src/colonio_go.d.ts GOOS=js GOARCH=wasm go build -o ./dist/oinari.wasm ./cmd/agent/*.go + GOOS=js GOARCH=wasm go build -o ./dist/sample.wasm ./cmd/sample/*.go GOOS=js GOARCH=wasm go test -o ./dist/test_crosslink.wasm -c ./agent/crosslink/* npm run build @@ -30,6 +31,9 @@ test: $(COLONIO_FILES) $(GO_FILES) $(OINARI_FILES) bin/seed src/keys.ts bin/seed: $(GO_FILES) go build -o $@ ./cmd/seed +dist/404.html: src/404.html keys.json + go run ./cmd/tool template -i src/404.html -v keys.json > $@ + dist/colonio.js: build/colonio/output/colonio.js cp $< $@ @@ -39,6 +43,9 @@ dist/colonio.wasm: build/colonio/output/colonio.wasm dist/colonio_go.js: build/colonio/src/js/colonio_go.js cp $< $@ +dist/error.html: src/error.html keys.json + go run ./cmd/tool template -i src/error.html -v keys.json > $@ + dist/index.html: src/index.html keys.json go run ./cmd/tool template -i src/index.html -v keys.json > $@ diff --git a/agent/core/driver.go b/agent/core/driver.go deleted file mode 100644 index bb9cd84..0000000 --- a/agent/core/driver.go +++ /dev/null @@ -1,8 +0,0 @@ -package core - -type GlobalCommandDriver interface { - EncouragePod(nid, uuid string) error -} - -type LocalCommandDriver interface { -} diff --git a/agent/core/pod.go b/agent/core/pod.go deleted file mode 100644 index d3caf4f..0000000 --- a/agent/core/pod.go +++ /dev/null @@ -1,58 +0,0 @@ -package core - -import ( - "context" - "encoding/json" - "log" - - "github.com/llamerada-jp/colonio/go/colonio" -) - -type PodImpl struct { - uuid string -} - -func NewPod(uuid string) *PodImpl { - return &PodImpl{ - uuid: uuid, - } -} - -// return false if the pod can be deleted. -func (p *PodImpl) Update(ctx context.Context, colonio colonio.Colonio) (bool, error) { - log.Println("update", p.uuid) - key := string(ResourceTypePod) + "/" + p.uuid - v, err := colonio.KvsGet(key) - if err != nil { - return false, err - } - - s, err := v.GetString() - if err != nil { - return false, err - } - - var pod Pod - err = json.Unmarshal([]byte(s), &pod) - if err != nil { - return false, err - } - - localNid := colonio.GetLocalNid() - if pod.Status.RunningNode == localNid { - log.Println("fixme!") - return true, nil - } - - if pod.Status.RunningNode == "" { - log.Println("fixme!") - return true, nil - } - - return false, nil -} - -func (p *PodImpl) HasLock() bool { - // TODO return lock status of lease - return true -} diff --git a/agent/core/system.go b/agent/core/system.go deleted file mode 100644 index b22c66b..0000000 --- a/agent/core/system.go +++ /dev/null @@ -1,241 +0,0 @@ -package core - -import ( - "context" - "encoding/json" - "fmt" - "log" - "math" - "math/rand" - "strings" - "time" - - "github.com/google/uuid" - - "github.com/llamerada-jp/colonio/go/colonio" -) - -type SystemEventHandler interface { - OnConnect(sys *System) error -} - -type System struct { - online bool - colonio colonio.Colonio - evh SystemEventHandler - gcd GlobalCommandDriver - lcd LocalCommandDriver - pods map[string]*PodImpl -} - -func init() { - rand.Seed(time.Now().UnixMicro()) -} - -func NewSystem(col colonio.Colonio, evh SystemEventHandler, gcd GlobalCommandDriver, lcd LocalCommandDriver) *System { - return &System{ - online: false, - colonio: col, - evh: evh, - gcd: gcd, - lcd: lcd, - pods: make(map[string]*PodImpl), - } -} - -func (sys *System) Start(ctx context.Context) error { - ticker := time.NewTicker(time.Second * 3) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return nil - - case <-ticker.C: - if !sys.online { - continue - } - err := sys.loop(ctx) - if err != nil { - log.Println(err) - } - } - } -} - -func (sys *System) loop(ctx context.Context) error { - err := sys.dealResources() - if err != nil { - return err - } - - for uuid, pod := range sys.pods { - effective, err := pod.Update(ctx, sys.colonio) - if err != nil { - return err - } - if !effective { - delete(sys.pods, uuid) - } - } - - return nil -} - -func (sys *System) dealResources() error { - resources := make([]struct { - resourceType ResourceType - js string - }, 0) - - // to avoid dead-lock of ForeachLocalValue, don't call colonio's method in the callback func - localData := sys.colonio.KvsGetLocalData() - defer localData.Free() - for _, key := range localData.GetKeys() { - v, err := localData.GetValue(key) - if err != nil { - return err - } - js, err := v.GetString() - if err != nil { - return err - } - resourceEntry := strings.Split(key, "/") - if len(resourceEntry) != 2 { - return fmt.Errorf("local value key is not supported format:%s", key) - } - resources = append(resources, struct { - resourceType ResourceType - js string - }{ - resourceType: ResourceType(resourceEntry[0]), - js: js, - }) - } - - for _, resource := range resources { - err := sys.dealResource(resource.resourceType, resource.js) - if err != nil { - log.Println(err) - } - } - return nil -} - -func (sys *System) dealResource(t ResourceType, js string) error { - switch t { - case ResourceTypePod: - var pod Pod - err := json.Unmarshal([]byte(js), &pod) - if err != nil { - return err - } - err = sys.schedulePod(&pod) - if err != nil { - return err - } - - default: - log.Printf("debug unhandled resource:%s", string(t)) - } - return nil -} - -func (sys *System) schedulePod(pod *Pod) error { - if pod.Status.RunningNode != "" { - return nil - } - - switch pod.Spec.Scheduler.Type { - case "creator": - err := sys.gcd.EncouragePod(pod.Spec.CreatorNode, pod.Meta.Uuid) - return err - - default: - return fmt.Errorf("unsupported scheduling policy:%s", pod.Spec.Scheduler.Type) - } -} - -func (sys *System) Connect(url, token string) error { - err := sys.colonio.Connect(url, token) - if err != nil { - return err - } - - err = sys.evh.OnConnect(sys) - if err != nil { - return err - } - - sys.online = true - return nil -} - -func (sys *System) ApplyPod(name, image string) (string, error) { - pod := Pod{ - Meta: ObjectMeta{ - Type: ResourceTypePod, - Name: name, - }, - Spec: PodSpec{ - CreatorNode: sys.colonio.GetLocalNid(), - // Support single container pod temporary. - Containers: []ContainerSpec{ - { - Name: "main", - Image: image, - }, - }, - Scheduler: SchedulerSpec{ - Type: "creator", - }, - }, - } - // Retry with new uuid if the pod having the same uuid is exist. - for { - pod.Meta.Uuid = uuid.Must(uuid.NewRandom()).String() - key := string(ResourceTypePod) + "/" + pod.Meta.Uuid - js, err := json.Marshal(pod) - if err != nil { - return "", err - } - err = sys.colonio.KvsSet(key, string(js), colonio.KvsProhibitOverwrite) - // TODO: retry only if the same uuid id exists - if err == nil { - return pod.Meta.Uuid, nil - } - } -} - -func (sys *System) Terminate(uuid string) error { - // TODO - log.Println("fix it") - return nil -} - -func (sys *System) SetPosition(latitude, longitude float64) error { - // convert L/L to radian - _, _, err := sys.colonio.SetPosition(longitude*math.Pi/180.0, latitude*math.Pi/90.0) - return err -} - -func (sys *System) EncouragePod(ctx context.Context, uuid string) error { - // skip if pod exists - if _, ok := sys.pods[uuid]; ok { - return nil - } - - // create and update pod - pod := NewPod(uuid) - sys.pods[uuid] = pod - effective, err := pod.Update(ctx, sys.colonio) - if err != nil { - return err - } - if !effective { - delete(sys.pods, uuid) - } - - return nil -} diff --git a/agent/core/types.go b/agent/core/types.go deleted file mode 100644 index 8f6d827..0000000 --- a/agent/core/types.go +++ /dev/null @@ -1,38 +0,0 @@ -package core - -type ResourceType string - -const ( - ResourceTypePod = ResourceType("pod") -) - -type ObjectMeta struct { - Type ResourceType `json:"type"` - Name string `json:"name"` - Uuid string `json:"uuid"` -} - -type Pod struct { - Meta ObjectMeta `json:"meta"` - Spec PodSpec `json:"spec"` - Status PodStatus `json:"status"` -} - -type PodSpec struct { - CreatorNode string `json:"creatorNode"` - Containers []ContainerSpec `json:"containers"` - Scheduler SchedulerSpec `json:"scheduler"` -} - -type ContainerSpec struct { - Name string `json:"name"` - Image string `json:"image"` -} - -type SchedulerSpec struct { - Type string `json:"type"` -} - -type PodStatus struct { - RunningNode string `json:"runningNode"` -} diff --git a/agent/cri/driver.go b/agent/cri/driver.go index 61d9c3b..545a2cc 100644 --- a/agent/cri/driver.go +++ b/agent/cri/driver.go @@ -22,38 +22,39 @@ func NewCRI(cl crosslink.Crosslink) CRI { } func criCallHelper[REQ any, RES any](ci *criImpl, path string, request *REQ) (*RES, error) { - type ResErr struct { - res *RES - err error - } reqJson, err := json.Marshal(request) if err != nil { return nil, err } - ch := make(chan *ResErr) + ch := make(chan *RES) + defer close(ch) + var funcError error ci.cl.Call(string(reqJson), map[string]string{ crosslink.TAG_PATH: strings.Join([]string{crosslinkPath, path}, "/"), }, func(result string, err error) { if err != nil { - ch <- &ResErr{nil, err} - return + funcError = err + close(ch) } var res RES err = json.Unmarshal([]byte(result), &res) if err != nil { - ch <- &ResErr{nil, err} - return + funcError = err + close(ch) } - ch <- &ResErr{&res, nil} + ch <- &res }) - resErr := <-ch - return resErr.res, resErr.err + res, ok := <-ch + if !ok { + return nil, funcError + } + return res, nil } func (ci *criImpl) RunPodSandbox(request *RunPodSandboxRequest) (*RunPodSandboxResponse, error) { diff --git a/agent/global/driver.go b/agent/global/driver.go deleted file mode 100644 index 36781c8..0000000 --- a/agent/global/driver.go +++ /dev/null @@ -1,32 +0,0 @@ -package global - -import ( - "encoding/json" - - "github.com/llamerada-jp/colonio/go/colonio" - "github.com/llamerada-jp/oinari/agent/core" -) - -type driverImpl struct { - colonio colonio.Colonio -} - -func NewCommandDriver(col colonio.Colonio) core.GlobalCommandDriver { - return &driverImpl{ - colonio: col, - } -} - -func (d *driverImpl) EncouragePod(nid, uuid string) error { - js, err := json.Marshal(encouragePod{ - Uuid: uuid, - }) - if err != nil { - return err - } - _, err = d.colonio.MessagingPost(nid, "encouragePod", string(js), 0) - if err != nil { - return err - } - return nil -} diff --git a/agent/global/handler.go b/agent/global/handler.go deleted file mode 100644 index 50c881a..0000000 --- a/agent/global/handler.go +++ /dev/null @@ -1,34 +0,0 @@ -package global - -import ( - "context" - "encoding/json" - "log" - - "github.com/llamerada-jp/colonio/go/colonio" - "github.com/llamerada-jp/oinari/agent/core" -) - -func InitHandler(system *core.System, col colonio.Colonio) error { - col.MessagingSetHandler("encouragePod", func(mr *colonio.MessagingRequest, mrw colonio.MessagingResponseWriter) { - js, err := mr.Message.GetString() - defer mrw.Write(nil) - if err != nil { - log.Println(err) - return - } - var param encouragePod - err = json.Unmarshal([]byte(js), ¶m) - if err != nil { - log.Println(err) - return - } - - err = system.EncouragePod(context.Background(), param.Uuid) - if err != nil { - log.Println(err) - return - } - }) - return nil -} diff --git a/agent/global/types.go b/agent/global/types.go deleted file mode 100644 index b746c90..0000000 --- a/agent/global/types.go +++ /dev/null @@ -1,5 +0,0 @@ -package global - -type encouragePod struct { - Uuid string `json:"uuid"` -} diff --git a/agent/local/driver.go b/agent/local/driver.go deleted file mode 100644 index f4223a7..0000000 --- a/agent/local/driver.go +++ /dev/null @@ -1,16 +0,0 @@ -package local - -import ( - "github.com/llamerada-jp/oinari/agent/core" - "github.com/llamerada-jp/oinari/agent/crosslink" -) - -type driverImpl struct { - cl crosslink.Crosslink -} - -func NewCommandDriver(cl crosslink.Crosslink) core.LocalCommandDriver { - return &driverImpl{ - cl: cl, - } -} diff --git a/agent/local/handler.go b/agent/local/handler.go deleted file mode 100644 index c4a922e..0000000 --- a/agent/local/handler.go +++ /dev/null @@ -1,74 +0,0 @@ -package local - -import ( - "github.com/llamerada-jp/oinari/agent/core" - "github.com/llamerada-jp/oinari/agent/crosslink" -) - -type connectParam struct { - Url string `json:"url"` - Token string `json:"token"` -} - -type applyPodParam struct { - Name string `json:"name"` - Image string `json:"image"` -} - -type applyPodResult struct { - Uuid string `json:"uuid"` -} - -type setPositionParam struct { - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` -} - -type terminateParam struct { - Uuid string `json:"uuid"` -} - -func InitHandler(system *core.System, rootHandler crosslink.MultiPlexer) error { - mpx := crosslink.NewMultiPlexer() - rootHandler.SetHandler("system", mpx) - - mpx.SetHandler("connect", crosslink.NewFuncObjHandler(func(param *connectParam, tags map[string]string, writer crosslink.ResponseObjWriter) { - err := system.Connect(param.Url, param.Token) - if err != nil { - writer.ReplyError(err.Error()) - return - } - writer.ReplySuccess(nil) - })) - - mpx.SetHandler("applyPod", crosslink.NewFuncObjHandler(func(param *applyPodParam, tags map[string]string, writer crosslink.ResponseObjWriter) { - uuid, err := system.ApplyPod(param.Name, param.Image) - if err != nil { - writer.ReplyError((err.Error())) - return - } - writer.ReplySuccess(applyPodResult{ - Uuid: uuid, - }) - })) - - mpx.SetHandler("setPosition", crosslink.NewFuncObjHandler(func(param *setPositionParam, tags map[string]string, writer crosslink.ResponseObjWriter) { - err := system.SetPosition(param.Latitude, param.Longitude) - if err != nil { - writer.ReplyError(err.Error()) - return - } - writer.ReplySuccess(nil) - })) - - mpx.SetHandler("terminate", crosslink.NewFuncObjHandler(func(param *terminateParam, tags map[string]string, writer crosslink.ResponseObjWriter) { - err := system.Terminate(param.Uuid) - if err != nil { - writer.ReplyError(err.Error()) - return - } - writer.ReplySuccess(nil) - })) - - return nil -} diff --git a/agent/local/types.go b/agent/local/types.go deleted file mode 100644 index 469c3dc..0000000 --- a/agent/local/types.go +++ /dev/null @@ -1 +0,0 @@ -package local diff --git a/agent/resource/account/kvs.go b/agent/resource/account/kvs.go new file mode 100644 index 0000000..0604103 --- /dev/null +++ b/agent/resource/account/kvs.go @@ -0,0 +1,121 @@ +package account + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/llamerada-jp/colonio/go/colonio" + "github.com/llamerada-jp/oinari/api" +) + +type KvsDriver interface { + setMeta(name string) error + bindPod(pod *api.Pod) error +} + +type kvsDriverImpl struct { + name string + col colonio.Colonio +} + +func NewKvsDriver(col colonio.Colonio) KvsDriver { + return &kvsDriverImpl{ + col: col, + } +} + +func (kvs *kvsDriverImpl) setMeta(name string) error { + kvs.name = name + + account, err := kvs.getOrCreate(name) + if err != nil { + return err + } + + if account.Meta.Name != name { + return fmt.Errorf("account uuid collision") + } + + return nil +} + +func (kvs *kvsDriverImpl) bindPod(pod *api.Pod) error { + account, err := kvs.getOrCreate(kvs.name) + if err != nil { + return err + } + + node, ok := account.Status.Pods[pod.Meta.Uuid] + if ok && node == pod.Status.RunningNode { + return nil + } + + account.Status.Pods[pod.Meta.Uuid] = pod.Status.RunningNode + return kvs.set(account) +} + +func (kvs *kvsDriverImpl) getOrCreate(name string) (*api.Account, error) { + key := kvs.getKey(name) + val, err := kvs.col.KvsGet(key) + if err == nil { + raw, err := val.GetBinary() + if err != nil { + return nil, err + } + + var account api.Account + err = json.Unmarshal(raw, &account) + if err != nil { + return nil, err + } + + return &account, nil + } + // TODO: check does account data exist? + + account := &api.Account{ + Meta: &api.ObjectMeta{ + Type: api.ResourceTypeAccount, + Name: name, + Account: name, + Uuid: kvs.getUUID(name), + }, + Status: &api.AccountStatus{ + Pods: make(map[string]string), + }, + } + + err = kvs.set(account) + if err != nil { + return nil, err + } + + return account, nil +} + +func (kvs *kvsDriverImpl) set(account *api.Account) error { + raw, err := json.Marshal(account) + if err != nil { + return err + } + + err = kvs.col.KvsSet(kvs.getKey(account.Meta.Name), raw, 0) + if err != nil { + return err + } + + return nil +} + +// use sha256 hash as account's uuid +func (kvs *kvsDriverImpl) getKey(name string) string { + return string(api.ResourceTypeAccount) + "/" + kvs.getUUID(name) +} + +// use sha256 hash as account's uuid +func (kvs *kvsDriverImpl) getUUID(name string) string { + hash := sha256.Sum256([]byte(kvs.name)) + return hex.EncodeToString(hash[:]) +} diff --git a/agent/resource/account/manager.go b/agent/resource/account/manager.go new file mode 100644 index 0000000..d85d207 --- /dev/null +++ b/agent/resource/account/manager.go @@ -0,0 +1,35 @@ +package account + +import ( + "github.com/llamerada-jp/oinari/api" +) + +type Manager interface { + GetAccountName() string + Refresh() error + BindPod(pod *api.Pod) error +} + +type ManagerImpl struct { + name string + kvs KvsDriver +} + +func NewManager(account string, kvs KvsDriver) *ManagerImpl { + return &ManagerImpl{ + name: account, + kvs: kvs, + } +} + +func (mgr *ManagerImpl) GetAccountName() string { + return mgr.name +} + +func (mgr *ManagerImpl) Refresh() error { + return mgr.kvs.setMeta(mgr.name) +} + +func (mgr *ManagerImpl) BindPod(pod *api.Pod) error { + return mgr.kvs.bindPod(pod) +} diff --git a/agent/resource/local_datastore.go b/agent/resource/local_datastore.go new file mode 100644 index 0000000..d163faf --- /dev/null +++ b/agent/resource/local_datastore.go @@ -0,0 +1,57 @@ +package resource + +import ( + "fmt" + "strings" + + "github.com/llamerada-jp/colonio/go/colonio" + "github.com/llamerada-jp/oinari/api" +) + +type LocalResource struct { + ResourceType api.ResourceType + RecordRaw string +} + +type LocalDatastore interface { + GetResources() ([]LocalResource, error) +} + +type localDatastore struct { + col colonio.Colonio +} + +func NewLocalDatastore(col colonio.Colonio) LocalDatastore { + return &localDatastore{ + col: col, + } +} + +func (ld *localDatastore) GetResources() ([]LocalResource, error) { + resources := make([]LocalResource, 0) + + // to avoid dead-lock of ForeachLocalValue, don't call colonio's method in the callback func + localData := ld.col.KvsGetLocalData() + defer localData.Free() + + for _, key := range localData.GetKeys() { + v, err := localData.GetValue(key) + if err != nil { + return nil, err + } + js, err := v.GetString() + if err != nil { + return nil, err + } + resourceEntry := strings.Split(key, "/") + if len(resourceEntry) != 2 { + return nil, fmt.Errorf("local value key is not supported format:%s", key) + } + resources = append(resources, LocalResource{ + ResourceType: api.ResourceType(resourceEntry[0]), + RecordRaw: js, + }) + } + + return resources, nil +} diff --git a/agent/resource/manager.go b/agent/resource/manager.go new file mode 100644 index 0000000..c9a7677 --- /dev/null +++ b/agent/resource/manager.go @@ -0,0 +1,68 @@ +package resource + +import ( + "context" + "log" + "time" + + "github.com/llamerada-jp/oinari/agent/resource/pod" + "github.com/llamerada-jp/oinari/api" +) + +type Manager interface { + Start(ctx context.Context) error +} + +type manager struct { + ld LocalDatastore + podMgr pod.Manager +} + +func NewManager(ld LocalDatastore, podMtr pod.Manager) Manager { + return &manager{ + ld: ld, + podMgr: podMtr, + } +} + +func (mgr *manager) Start(ctx context.Context) error { + ticker := time.NewTicker(time.Second * 3) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return nil + + case <-ticker.C: + err := mgr.loop(ctx) + if err != nil { + log.Println(err) + } + } + } +} + +func (mgr *manager) loop(ctx context.Context) error { + resources, err := mgr.ld.GetResources() + if err != nil { + return err + } + + for _, resource := range resources { + switch resource.ResourceType { + case api.ResourceTypePod: + err = mgr.podMgr.DealLocalResource([]byte(resource.RecordRaw)) + } + if err != nil { + return err + } + } + + err = mgr.podMgr.Loop(ctx) + if err != nil { + return err + } + + return nil +} diff --git a/agent/resource/node/manager.go b/agent/resource/node/manager.go new file mode 100644 index 0000000..32b9b50 --- /dev/null +++ b/agent/resource/node/manager.go @@ -0,0 +1,19 @@ +package node + +type Manager interface { + GetNid() string +} + +type ManagerImpl struct { + localNid string +} + +func NewManager(localNid string) *ManagerImpl { + return &ManagerImpl{ + localNid: localNid, + } +} + +func (mgr *ManagerImpl) GetNid() string { + return mgr.localNid +} diff --git a/agent/resource/pod/command_handler.go b/agent/resource/pod/command_handler.go new file mode 100644 index 0000000..bf4906e --- /dev/null +++ b/agent/resource/pod/command_handler.go @@ -0,0 +1,71 @@ +package pod + +import ( + "github.com/llamerada-jp/oinari/agent/crosslink" + "github.com/llamerada-jp/oinari/api" +) + +type applicationDigest struct { + Name string `json:"name"` + Uuid string `json:"uuid"` + RunningNode string `json:"runningNode"` + Owner string `json:"owner"` +} + +type runRequest struct { + Name string `json:"name"` + Spec *api.PodSpec `json:"spec"` +} + +type runResponse struct { + Digest *applicationDigest `json:"digest"` +} + +type listResponse struct { + Digests []applicationDigest `json:"digests"` +} + +type terminateRequest struct { + Uuid string `json:"uuid"` +} + +func InitCommandHandler(manager *ManagerImpl, rootHandler crosslink.MultiPlexer) error { + mpx := crosslink.NewMultiPlexer() + rootHandler.SetHandler("pod_manager", mpx) + + mpx.SetHandler("run", crosslink.NewFuncObjHandler( + func(request *runRequest, tags map[string]string, writer crosslink.ResponseObjWriter) { + digest, err := manager.run(request.Name, request.Spec) + if err != nil { + writer.ReplyError(err.Error()) + return + } + writer.ReplySuccess(runResponse{ + Digest: digest, + }) + })) + + mpx.SetHandler("list", crosslink.NewFuncObjHandler( + func(request *interface{}, tags map[string]string, writer crosslink.ResponseObjWriter) { + list, err := manager.list() + if err != nil { + writer.ReplyError(err.Error()) + return + } + writer.ReplySuccess(listResponse{ + Digests: list, + }) + })) + + mpx.SetHandler("terminate", crosslink.NewFuncObjHandler( + func(param *terminateRequest, tags map[string]string, writer crosslink.ResponseObjWriter) { + err := manager.terminate(param.Uuid) + if err != nil { + writer.ReplyError(err.Error()) + return + } + writer.ReplySuccess(nil) + })) + + return nil +} diff --git a/agent/resource/pod/kvs.go b/agent/resource/pod/kvs.go new file mode 100644 index 0000000..af63e8d --- /dev/null +++ b/agent/resource/pod/kvs.go @@ -0,0 +1,31 @@ +package pod + +import ( + "encoding/json" + + "github.com/llamerada-jp/colonio/go/colonio" + "github.com/llamerada-jp/oinari/api" +) + +type KvsDriver interface { + CreatePod(pod *api.Pod) error +} + +type kvsDriverImpl struct { + col colonio.Colonio +} + +func NewKvsDriver(col colonio.Colonio) KvsDriver { + return &kvsDriverImpl{ + col: col, + } +} + +func (kvs *kvsDriverImpl) CreatePod(pod *api.Pod) error { + key := string(api.ResourceTypePod) + "/" + pod.Meta.Uuid + raw, err := json.Marshal(pod) + if err != nil { + return err + } + return kvs.col.KvsSet(key, raw, colonio.KvsProhibitOverwrite) +} diff --git a/agent/resource/pod/manager.go b/agent/resource/pod/manager.go new file mode 100644 index 0000000..253f800 --- /dev/null +++ b/agent/resource/pod/manager.go @@ -0,0 +1,186 @@ +package pod + +import ( + "context" + "encoding/json" + "fmt" + "log" + + "github.com/google/uuid" + "github.com/llamerada-jp/colonio/go/colonio" + "github.com/llamerada-jp/oinari/agent/cri" + "github.com/llamerada-jp/oinari/agent/resource/account" + "github.com/llamerada-jp/oinari/agent/resource/node" + "github.com/llamerada-jp/oinari/api" +) + +type Manager interface { + DealLocalResource(raw []byte) error + Loop(ctx context.Context) error +} + +type ManagerImpl struct { + cri cri.CRI + kvs KvsDriver + messaging MessagingDriver + accountMgr account.Manager + nodeMgr node.Manager +} + +func NewManager(cri cri.CRI, kvs KvsDriver, messaging MessagingDriver, accountMgr account.Manager, nodeMgr node.Manager) *ManagerImpl { + return &ManagerImpl{ + cri: cri, + kvs: kvs, + messaging: messaging, + accountMgr: accountMgr, + nodeMgr: nodeMgr, + } +} + +func (mgr *ManagerImpl) DealLocalResource(raw []byte) error { + var pod api.Pod + err := json.Unmarshal(raw, &pod) + if err != nil { + return err + } + + err = mgr.accountMgr.BindPod(&pod) + if err != nil { + return err + } + + err = mgr.schedulePod(&pod) + if err != nil { + return err + } + + return nil +} + +func (mgr *ManagerImpl) Loop(ctx context.Context) error { + + for uuid, pod := range sys.pods { + effective, err := pod.Update(ctx, sys.colonio) + if err != nil { + return err + } + if !effective { + delete(sys.pods, uuid) + } + } + + return nil +} + +func (mgr *ManagerImpl) run(name string, spec *api.PodSpec) (*applicationDigest, error) { + pod := &api.Pod{ + Meta: &api.ObjectMeta{ + Type: api.ResourceTypePod, + Name: name, + Account: mgr.accountMgr.GetAccountName(), + CreatorNode: mgr.nodeMgr.GetNid(), + Uuid: uuid.Must(uuid.NewRandom()).String(), + }, + Spec: mgr.setDefaultPodSpec(spec), + } + + err := mgr.kvs.CreatePod(pod) + // TODO: retry only if the same uuid id exists + if err != nil { + return nil, err + } + + return &applicationDigest{ + Name: name, + Uuid: pod.Meta.Uuid, + Owner: pod.Meta.Account, + }, nil +} + +func (mgr *ManagerImpl) list() ([]applicationDigest, error) { + log.Fatal("TODO") + return nil, fmt.Errorf("TODO") +} + +func (mgr *ManagerImpl) terminate(uuid string) error { + log.Fatal("TODO") + return fmt.Errorf("TODO") +} + +func (mgr *ManagerImpl) setDefaultPodSpec(spec *api.PodSpec) *api.PodSpec { + if spec.Scheduler == nil { + spec.Scheduler = &api.SchedulerSpec{ + Type: "creator", + } + } + return spec +} + +func (mgr *ManagerImpl) schedulePod(pod *api.Pod) error { + if pod.Status.RunningNode != "" { + return nil + } + + switch pod.Spec.Scheduler.Type { + case "creator": + err := mgr.messaging.EncouragePod(pod.Meta.CreatorNode, pod.Meta.Uuid) + return err + + default: + return fmt.Errorf("unsupported scheduling policy:%s", pod.Spec.Scheduler.Type) + } +} + +func (mgr *ManagerImpl) EncouragePod(ctx context.Context, uuid string) error { + // skip if pod exists + if _, ok := sys.pods[uuid]; ok { + return nil + } + + // create and update pod + pod := NewPod(uuid) + sys.pods[uuid] = pod + effective, err := pod.Update(ctx, sys.colonio) + if err != nil { + return err + } + if !effective { + delete(sys.pods, uuid) + } + + return nil +} + +// return false if the pod can be deleted. +func (p *PodImpl) Update(ctx context.Context, colonio colonio.Colonio) (bool, error) { + log.Println("update", p.uuid) + key := string(ResourceTypePod) + "/" + p.uuid + v, err := colonio.KvsGet(key) + if err != nil { + return false, err + } + + s, err := v.GetString() + if err != nil { + return false, err + } + + var pod Pod + err = json.Unmarshal([]byte(s), &pod) + if err != nil { + return false, err + } + + localNid := colonio.GetLocalNid() + if pod.Status.RunningNode == localNid { + log.Println("fixme!") + return true, nil + } + + if pod.Status.RunningNode == "" { + log.Println("fixme!") + return true, nil + } + + return false, nil +} diff --git a/agent/resource/pod/messaging.go b/agent/resource/pod/messaging.go new file mode 100644 index 0000000..5829542 --- /dev/null +++ b/agent/resource/pod/messaging.go @@ -0,0 +1,65 @@ +package pod + +import ( + "context" + "encoding/json" + "log" + + "github.com/llamerada-jp/colonio/go/colonio" +) + +type MessagingDriver interface { + EncouragePod(nid, uuid string) error +} + +type messagingDriverImpl struct { + colonio colonio.Colonio +} + +type encouragePod struct { + Uuid string `json:"uuid"` +} + +func InitMessagingHandler(mgr *ManagerImpl, col colonio.Colonio) error { + col.MessagingSetHandler("encouragePod", func(mr *colonio.MessagingRequest, mrw colonio.MessagingResponseWriter) { + js, err := mr.Message.GetString() + defer mrw.Write(nil) + if err != nil { + log.Println(err) + return + } + var param encouragePod + err = json.Unmarshal([]byte(js), ¶m) + if err != nil { + log.Println(err) + return + } + + err = mgr.encouragePod(context.Background(), param.Uuid) + if err != nil { + log.Println(err) + return + } + }) + return nil +} + +func NewMessagingDriver(col colonio.Colonio) MessagingDriver { + return &messagingDriverImpl{ + colonio: col, + } +} + +func (d *messagingDriverImpl) EncouragePod(nid, uuid string) error { + js, err := json.Marshal(encouragePod{ + Uuid: uuid, + }) + if err != nil { + return err + } + _, err = d.colonio.MessagingPost(nid, "encouragePod", string(js), 0) + if err != nil { + return err + } + return nil +} diff --git a/agent/system/command_driver.go b/agent/system/command_driver.go new file mode 100644 index 0000000..72de683 --- /dev/null +++ b/agent/system/command_driver.go @@ -0,0 +1,33 @@ +package system + +import ( + "log" + + "github.com/llamerada-jp/oinari/agent/crosslink" +) + +type CommandDriver interface { + // send a message that tell initialization complete + TellInitComplete() error +} + +type commandDriver struct { + cl crosslink.Crosslink +} + +func NewCommandDriver(cl crosslink.Crosslink) CommandDriver { + return &commandDriver{ + cl: cl, + } +} + +func (cd *commandDriver) TellInitComplete() error { + cd.cl.Call("", map[string]string{ + crosslink.TAG_PATH: "system/onInitComplete", + }, func(result string, err error) { + if err != nil { + log.Fatalln(err) + } + }) + return nil +} diff --git a/agent/system/command_handler.go b/agent/system/command_handler.go new file mode 100644 index 0000000..d0218d5 --- /dev/null +++ b/agent/system/command_handler.go @@ -0,0 +1,39 @@ +package system + +import "github.com/llamerada-jp/oinari/agent/crosslink" + +type connectRequest struct { + Url string `json:"url"` + Account string `json:"account"` + Token string `json:"token"` +} + +type setPositionRequest struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + +func InitCommandHandler(sys *SystemImpl, rootMpx crosslink.MultiPlexer) error { + mpx := crosslink.NewMultiPlexer() + rootMpx.SetHandler("system", mpx) + + mpx.SetHandler("connect", crosslink.NewFuncObjHandler(func(request *connectRequest, tags map[string]string, writer crosslink.ResponseObjWriter) { + err := sys.connect(request.Url, request.Account, request.Token) + if err != nil { + writer.ReplyError(err.Error()) + return + } + writer.ReplySuccess(nil) + })) + + mpx.SetHandler("setPosition", crosslink.NewFuncObjHandler(func(request *setPositionRequest, tags map[string]string, writer crosslink.ResponseObjWriter) { + err := sys.setPosition(request.Latitude, request.Longitude) + if err != nil { + writer.ReplyError(err.Error()) + return + } + writer.ReplySuccess(nil) + })) + + return nil +} diff --git a/agent/system/system.go b/agent/system/system.go new file mode 100644 index 0000000..40f1379 --- /dev/null +++ b/agent/system/system.go @@ -0,0 +1,74 @@ +package system + +import ( + "context" + "math" + "math/rand" + "time" + + "github.com/llamerada-jp/colonio/go/colonio" +) + +type EventHandler interface { + OnConnect() error +} + +type System interface { + Start(ctx context.Context) error + TellInitComplete() error + GetAccount() string +} + +type SystemImpl struct { + colonio colonio.Colonio + evh EventHandler + cd CommandDriver + account string +} + +func init() { + rand.Seed(time.Now().UnixMicro()) +} + +func NewSystem(col colonio.Colonio, evh EventHandler, cd CommandDriver) *SystemImpl { + return &SystemImpl{ + colonio: col, + evh: evh, + cd: cd, + } +} + +func (sys *SystemImpl) Start(ctx context.Context) error { + <-ctx.Done() + return nil +} + +func (sys *SystemImpl) TellInitComplete() error { + return sys.cd.TellInitComplete() +} + +func (sys *SystemImpl) GetAccount() string { + return sys.account +} + +func (sys *SystemImpl) connect(url, account, token string) error { + err := sys.colonio.Connect(url, token) + if err != nil { + return err + } + + sys.account = account + + err = sys.evh.OnConnect() + if err != nil { + return err + } + + return nil +} + +func (sys *SystemImpl) setPosition(latitude, longitude float64) error { + // convert L/L to radian + _, _, err := sys.colonio.SetPosition(longitude*math.Pi/180.0, latitude*math.Pi/90.0) + return err +} diff --git a/api/account.go b/api/account.go new file mode 100644 index 0000000..8c764bd --- /dev/null +++ b/api/account.go @@ -0,0 +1,10 @@ +package api + +type Account struct { + Meta *ObjectMeta `json:"meta"` + Status *AccountStatus `json:"status"` +} + +type AccountStatus struct { + Pods map[string]string `json:"pods"` +} diff --git a/api/meta.go b/api/meta.go new file mode 100644 index 0000000..937f6bd --- /dev/null +++ b/api/meta.go @@ -0,0 +1,17 @@ +package api + +type ResourceType string + +const ( + ResourceTypeAccount = ResourceType("account") + ResourceTypeNode = ResourceType("node") + ResourceTypePod = ResourceType("pod") +) + +type ObjectMeta struct { + Type ResourceType `json:"type"` + Name string `json:"name"` + Account string `json:"account"` + CreatorNode string `json:"creatorNode"` + Uuid string `json:"uuid"` +} diff --git a/api/pod.go b/api/pod.go new file mode 100644 index 0000000..3315866 --- /dev/null +++ b/api/pod.go @@ -0,0 +1,25 @@ +package api + +type Pod struct { + Meta *ObjectMeta `json:"meta"` + Spec *PodSpec `json:"spec"` + Status *PodStatus `json:"status"` +} + +type PodSpec struct { + Containers []ContainerSpec `json:"containers"` + Scheduler *SchedulerSpec `json:"scheduler"` +} + +type ContainerSpec struct { + Name string `json:"name"` + Image string `json:"image"` +} + +type SchedulerSpec struct { + Type string `json:"type"` +} + +type PodStatus struct { + RunningNode string `json:"runningNode"` +} diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 19da860..ee44514 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -17,149 +17,136 @@ package main import ( "context" - "fmt" "log" - "syscall/js" - "time" "github.com/llamerada-jp/colonio/go/colonio" - "github.com/llamerada-jp/oinari/agent/core" "github.com/llamerada-jp/oinari/agent/cri" "github.com/llamerada-jp/oinari/agent/crosslink" - "github.com/llamerada-jp/oinari/agent/global" - "github.com/llamerada-jp/oinari/agent/local" + "github.com/llamerada-jp/oinari/agent/resource" + "github.com/llamerada-jp/oinari/agent/resource/account" + "github.com/llamerada-jp/oinari/agent/resource/node" + "github.com/llamerada-jp/oinari/agent/resource/pod" + "github.com/llamerada-jp/oinari/agent/system" ) -const ( - gltfLoaderName = "gltfLoader" - fieldBinder = "newField" -) - -type asset struct { - jsAssert js.Value - err error -} - -type field struct { - jsField js.Value +type agent struct { + ctx context.Context + // crosslink + cl crosslink.Crosslink + rootMpx crosslink.MultiPlexer + // colonio + col colonio.Colonio + // system + sys system.System } -type object struct { - asset *asset - field *field - jsObject js.Value +func (agent *agent) initCrosslink() error { + agent.rootMpx = crosslink.NewMultiPlexer() + agent.cl = crosslink.NewCrosslink("crosslink", agent.rootMpx) + return nil } -func newGltfAssert(url string) *asset { - a := &asset{ - jsAssert: js.Null(), +func (agent *agent) initColonio() error { + config := colonio.NewConfig() + col, err := colonio.NewColonio(config) + if err != nil { + return err } - - js.Global().Call(gltfLoaderName, url, - js.FuncOf(func(_ js.Value, args []js.Value) interface{} { - // onLoad - a.jsAssert = args[0] - return nil - }), - js.Null(), // onProgress - js.FuncOf(func(this js.Value, args []js.Value) interface{} { - // onError - a.err = fmt.Errorf(args[0].String()) - return nil - })) - - return a + agent.col = col + return nil } -func (a *asset) hasError() error { - return a.err -} +func (agent *agent) initSystem() error { + cd := system.NewCommandDriver(agent.cl) + sys := system.NewSystem(agent.col, agent, cd) + agent.sys = sys + go func() { + err := agent.sys.Start(agent.ctx) + if err != nil { + log.Fatalln(err) + } + }() -func (a *asset) checkLoaded() bool { - return !a.jsAssert.IsNull() + system.InitCommandHandler(sys, agent.rootMpx) + return nil } -func newField() *field { - return &field{ - jsField: js.Global().Call(fieldBinder), - } -} +func (agent *agent) execute() error { + ctx, _ := context.WithCancel(context.Background()) + agent.ctx = ctx -func newObject(asset *asset, field *field) *object { - return &object{ - asset: asset, - field: field, - jsObject: js.Null(), + err := agent.initCrosslink() + if err != nil { + return err } -} -func (o *object) tryBind() bool { - // Already binded the object. - if !o.jsObject.IsNull() { - return true + err = agent.initColonio() + if err != nil { + return err } - // The asset has not loaded yet. - if !o.asset.checkLoaded() { - return false + err = agent.initSystem() + if err != nil { + return err } - o.jsObject = o.asset.jsAssert.Call("clone", js.ValueOf(true)) - o.field.jsField.Call("add", o.jsObject) + err = agent.sys.TellInitComplete() + if err != nil { + return err + } - return true -} + /* + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() -func initCrosslink() (crosslink.Crosslink, crosslink.MultiPlexer) { - mpxRoot := crosslink.NewMultiPlexer() - cl := crosslink.NewCrosslink("crosslink", mpxRoot) + for { + select { + case <-ctx.Done(): + return nil - return cl, mpxRoot + case <-ticker.C: + } + } + /*/ + <-ctx.Done() + return nil + //*/ } -func main() { - ticker := time.NewTicker(1 * time.Second) - defer ticker.Stop() - - ctx, _ := context.WithCancel(context.Background()) - - cl, mpxRoot := initCrosslink() - config := colonio.NewConfig() - col, err := colonio.NewColonio(config) - if err != nil { - log.Fatalln(err) - } - - gcd := global.NewCommandDriver(col) - lcd := local.NewCommandDriver(cl) - cri := cri.NewCRI(cl) - seh := newSystemEventHandler(col) - - sys := core.NewSystem(col, seh, gcd, lcd) +// implement system events +func (agent *agent) OnConnect() error { + // node manager + nodeMgr := node.NewManager(agent.col.GetLocalNid()) + + // account manager + accountKvs := account.NewKvsDriver(agent.col) + accountMgr := account.NewManager(agent.sys.GetAccount(), accountKvs) + + // pod manager + cri := cri.NewCRI(agent.cl) + podKvs := pod.NewKvsDriver(agent.col) + podMsg := pod.NewMessagingDriver(agent.col) + podMgr := pod.NewManager(cri, podKvs, podMsg, accountMgr, nodeMgr) + pod.InitCommandHandler(podMgr, agent.rootMpx) + pod.InitMessagingHandler(podMgr, agent.col) + + // resource manager + ld := resource.NewLocalDatastore(agent.col) + resourceManager := resource.NewManager(ld, podMgr) go func() { - err := sys.Start(ctx) + err := resourceManager.Start(agent.ctx) if err != nil { log.Fatalln(err) } }() - local.InitHandler(sys, mpxRoot) - - // send a message that tell initialization complete - cl.Call("", map[string]string{ - crosslink.TAG_PATH: "system/initializationComplete", - }, func(result string, err error) { - if err != nil { - log.Fatalln(err) - } - }) - - for { - select { - case <-ctx.Done(): - return + return nil +} - case <-ticker.C: - } +func main() { + agent := &agent{} + err := agent.execute() + if err != nil { + log.Fatalln(err) } } diff --git a/cmd/agent/system_event_handler.go b/cmd/agent/system_event_handler.go deleted file mode 100644 index b7c2b5f..0000000 --- a/cmd/agent/system_event_handler.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "github.com/llamerada-jp/colonio/go/colonio" - "github.com/llamerada-jp/oinari/agent/core" - "github.com/llamerada-jp/oinari/agent/global" -) - -type systemEventHandlerImpl struct { - colonio colonio.Colonio -} - -func newSystemEventHandler(col colonio.Colonio) core.SystemEventHandler { - return &systemEventHandlerImpl{ - colonio: col, - } -} - -func (s *systemEventHandlerImpl) OnConnect(sys *core.System) error { - return global.InitHandler(sys, s.colonio) -} diff --git a/cmd/sample/main.go b/cmd/sample/main.go new file mode 100644 index 0000000..262d9b6 --- /dev/null +++ b/cmd/sample/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "log" + "time" +) + +func main() { + ticker := time.NewTicker(time.Second * 3) + defer ticker.Stop() + + for range ticker.C { + log.Println("hello world") + } +} diff --git a/dist/sample.app.json b/dist/sample.app.json new file mode 100644 index 0000000..84e40e6 --- /dev/null +++ b/dist/sample.app.json @@ -0,0 +1,13 @@ +{ + "metadata": { + "name": "sample" + }, + "spec": { + "containers": [ + { + "name": "main", + "image": "./sample.container.json" + } + ] + } +} \ No newline at end of file diff --git a/dist/sample.container.json b/dist/sample.container.json new file mode 100644 index 0000000..3de2459 --- /dev/null +++ b/dist/sample.container.json @@ -0,0 +1,4 @@ +{ + "image": "sample.wasm", + "runtime": "go1.19" +} \ No newline at end of file diff --git a/dist/404.html b/src/404.html similarity index 97% rename from dist/404.html rename to src/404.html index 98b99ac..5353ce7 100644 --- a/dist/404.html +++ b/src/404.html @@ -59,7 +59,7 @@

page not found

function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); - gtag('config', 'UA-129756820-1'); + gtag('config', '{{.google_analytics_tracking_id}}'); \ No newline at end of file diff --git a/src/command.ts b/src/command.ts index d3ec638..7b169e9 100644 --- a/src/command.ts +++ b/src/command.ts @@ -1,39 +1,115 @@ import * as CL from "./crosslink"; +const CL_SYSTEM_PATH: string = "system"; +const CL_MANAGER_PATH: string = "pod_manager"; + +export interface ApplicationDigest { + name: string + uuid: string + runningNode: string + owner: string +} + +interface ApplicationDefinition { + metadata: ApplicationMetadata + spec: PodSpec +} + +interface ApplicationMetadata { + name: string +} + +interface ManagerRunRequest { + name: string + spec: PodSpec +} + +interface ManagerRunResponse { + digest: ApplicationDigest +} + +interface ManagerListResponse { + digests: Array +} + +interface ManagerTerminateRequest { + uuid: string +} + +interface ObjectMeta { + name: string + namespace: string | undefined +} + +interface Pod { + meta: ObjectMeta + spec: PodSpec +} + +interface PodSpec { + containers: Array +} + +interface Container { + name: string + image: string +} + export class Commands { private cl: CL.Crosslink; constructor(cl: CL.Crosslink) { this.cl = cl; } - connect(url: string, token: string): Promise { - return this.cl.call("system/connect", { + connect(url: string, account: string, token: string): Promise { + return this.cl.call(CL_SYSTEM_PATH + "/connect", { url: url, + account: account, token: token, }); } - applyPod(name: string, image: string): Promise<{ uuid: string }> { - return this.cl.call("system/applyPod", { - name: name, - image: image, - }).then((result) => { - return { - uuid: (result as any).uuid, - }; - }); - } - setPosition(lat: number, lon: number): Promise { - return this.cl.call("system/setPosition", { + return this.cl.call(CL_SYSTEM_PATH + "/setPosition", { latitude: lat, longitude: lon, }); } - terminate(uuid: string): Promise { - return this.cl.call("system/terminate", { - uuid: uuid, + runApplication(url: string, postfix?: string | undefined): Promise { + return fetch(url).then((response: Response) => { + if (!response.ok) { + throw new Error("Application could not start: " + response.statusText); + } + return response.json(); + + }).then((a) => { + let app = a as ApplicationDefinition; + let name = app.metadata.name; + if (postfix != null) { + name = name + postfix; + } + return this.cl.call(CL_MANAGER_PATH + "/run", { + name: name, + spec: app.spec + } as ManagerRunRequest); + + }).then((r) => { + let result = r as ManagerRunResponse; + return result.digest; }); } + + listApplications(): Promise> { + return this.cl.call(CL_MANAGER_PATH + "/list", {}).then((r) => { + let result = r as ManagerListResponse; + return result.digests; + }); + } + + terminateApplication(uuid: string): Promise { + return this.cl.call(CL_MANAGER_PATH + "/terminate", { + uuid: uuid, + } as ManagerTerminateRequest); + } } \ No newline at end of file diff --git a/dist/error.html b/src/error.html similarity index 100% rename from dist/error.html rename to src/error.html diff --git a/src/index.html b/src/index.html index 3a2980b..847ceba 100644 --- a/src/index.html +++ b/src/index.html @@ -13,7 +13,7 @@