From 70837391d1b792c291f1a164c187bb660c579dbf Mon Sep 17 00:00:00 2001 From: cuigh Date: Thu, 23 Dec 2021 14:40:14 +0800 Subject: [PATCH] Correct handling host and ingress networks when editing service --- CHANGELOG.md | 1 + biz/service.go | 30 +++++----- compose.yml | 98 +++++++++++++++++-------------- dao/bolt/session.go | 34 +++++++++++ dao/dao.go | 1 + dao/mongo/session.go | 18 ++++++ docker/docker.go | 17 ++++-- docker/network.go | 34 ++++++++--- docker/node.go | 10 +--- ui/src/components/chart/Chart.vue | 30 ++++------ ui/src/components/chart/chart.ts | 2 +- ui/src/pages/service/Edit.vue | 26 ++++---- ui/src/pages/service/View.vue | 9 +-- 13 files changed, 187 insertions(+), 123 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc74ce8..7b0247a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ > As this version contains some incompatible modifications, it is recommended to redeploy instead of upgrading directly. * feat: Refactor UI with vue3. +* feat: Add support to agent. Swirl can connect to any container even in swarm mode now. * feat: Switch to official MongoDB driver. * feat: Allow set chart margins. * fix: Some args are incorrect when generating service command line. diff --git a/biz/service.go b/biz/service.go index e15d094..7ece3fe 100644 --- a/biz/service.go +++ b/biz/service.go @@ -56,26 +56,31 @@ func (b *serviceBiz) Find(name string, status bool) (service *Service, raw strin } if err == nil { service = newService(&s) - //err = b.fillNetworks(service, &s) + err = b.fillNetworks(service) } return } -func (b *serviceBiz) fillNetworks(service *Service, s *swarm.Service) error { - if len(s.Endpoint.VirtualIPs) == 0 { +func (b *serviceBiz) fillNetworks(service *Service) error { + if len(service.Endpoint.VIPs) == 0 { return nil } - var ids = make([]string, len(s.Endpoint.VirtualIPs)) - for i, vip := range s.Endpoint.VirtualIPs { - ids[i] = vip.NetworkID + var ids = make([]string, len(service.Endpoint.VIPs)) + for i, vip := range service.Endpoint.VIPs { + ids[i] = vip.ID } + names, err := b.d.NetworkNames(context.TODO(), ids...) if err == nil { - for i, vip := range s.Endpoint.VirtualIPs { - ids[i] = names[vip.NetworkID] + ":" + vip.NetworkID + for i := range service.Endpoint.VIPs { + vip := &service.Endpoint.VIPs[i] + vip.Name = names[vip.ID] + // ingress network cannot be explicitly attached. + if vip.Name != "ingress" { + service.Networks = append(service.Networks, vip.Name) + } } - service.Networks = ids } return err } @@ -215,7 +220,7 @@ type Service struct { Env data.Options `json:"env,omitempty"` Labels data.Options `json:"labels,omitempty"` ContainerLabels data.Options `json:"containerLabels,omitempty"` - Networks []string `json:"networks,omitempty"` + Networks []string `json:"networks,omitempty"` // only for edit Mounts []Mount `json:"mounts,omitempty"` Update struct { State string `json:"state,omitempty"` @@ -548,11 +553,6 @@ func newService(s *swarm.Service) *Service { service.Update.Message = s.UpdateStatus.Message } - // Networks - for _, n := range s.Spec.TaskTemplate.Networks { - service.Networks = append(service.Networks, n.Target) - } - // Endpoint service.Endpoint.Mode = s.Endpoint.Spec.Mode for _, vip := range s.Endpoint.VirtualIPs { diff --git a/compose.yml b/compose.yml index 20294ac..0c7b5be 100644 --- a/compose.yml +++ b/compose.yml @@ -1,16 +1,16 @@ -version: '3' +version: '3.8' services: swirl: image: cuigh/swirl environment: - DB_ADDRESS: mongodb://mongo:27017/swirl + DB_ADDRESS: mongodb:// DOCKER_ENDPOINT: tcp://swirl_manager_agent:2375 AGENTS: swirl_manager_agent,swirl_worker_agent - ports: - - "8001:8001" networks: - net + ports: + - "8001:8001" deploy: replicas: 2 placement: @@ -18,10 +18,10 @@ services: manager_agent: image: cuigh/socat - volumes: - - /var/run/docker.sock:/var/run/docker.sock networks: - net + volumes: + - /var/run/docker.sock:/var/run/docker.sock deploy: mode: global placement: @@ -29,53 +29,63 @@ services: worker_agent: image: cuigh/socat - volumes: - - /var/run/docker.sock:/var/run/docker.sock networks: - net + volumes: + - /var/run/docker.sock:/var/run/docker.sock deploy: mode: global placement: constraints: [ node.role == worker ] - # prometheus: - # image: prom/prometheus - # volumes: - # - prometheus:/prometheus - # networks: - # - net - # deploy: - # replicas: 1 - # placement: - # constraints: [ node.labels.app.prometheus == true ] - - # cadvisor: - # image: gcr.io/cadvisor/cadvisor - # volumes: - # - /:/rootfs:ro - # - /sys:/sys:ro - # - /var/lib/docker:/var/lib/docker:ro - # - /var/run:/var/run:ro - # - /var/run/docker.sock:/var/run/docker.sock:ro - # networks: - # - net - # deploy: - # mode: global - - mongo: - image: mongo - volumes: - - mongo:/data/db - networks: - - net - deploy: - replicas: 1 +# prometheus: +# image: prom/prometheus +# networks: +# - net +# volumes: +# - prometheus:/prometheus +# configs: +# - source: prometheus.yml +# target: /etc/prometheus/prometheus.yml +# deploy: +# replicas: 1 # placement: -# constraints: [ node.labels.app.mongo == true ] +# constraints: [ node.labels.app.prometheus == true ] -volumes: - prometheus: - mongo: +# cadvisor: +# image: gcr.io/cadvisor/cadvisor +# networks: +# - net +# volumes: +# - /:/rootfs:ro +# - /dev/disk/:/dev/disk:ro +# - /sys:/sys:ro +# - /var/run:/var/run:ro +# - /var/lib/docker:/var/lib/docker:ro +# privileged: true +# deploy: +# mode: global +# +# node: +# image: quay.io/prometheus/node-exporter +# command: +# - '--path.rootfs=/host' +# networks: +# - host +# pid: host +# volumes: +# - /:/host:ro,rslave +# deploy: +# mode: global + +#volumes: +# prometheus: + +#configs: +# prometheus.yml: +# external: true networks: +# host: +# external: true net: \ No newline at end of file diff --git a/dao/bolt/session.go b/dao/bolt/session.go index f16351e..fee8ee6 100644 --- a/dao/bolt/session.go +++ b/dao/bolt/session.go @@ -38,6 +38,40 @@ func (d *Dao) SessionUpdateExpiry(ctx context.Context, id string, expiry time.Ti }) } +func (d *Dao) SessionUpdateDirty(ctx context.Context, userID string, roleID string) (err error) { + contains := func(arr []string, str string) bool { + for _, s := range arr { + if s == str { + return true + } + } + return false + } + + var ( + buf []byte + now = time.Now() + ) + return d.db.Update(func(tx *bolt.Tx) (err error) { + b := tx.Bucket([]byte(Session)) + return b.ForEach(func(k, v []byte) error { + session := &model.Session{} + if err = decode(v, session); err != nil { + return err + } + + if (userID != "" && session.UserID == userID) || (roleID != "" && contains(session.Roles, roleID)) { + session.Dirty = true + session.UpdatedAt = now + if buf, err = encode(session); err == nil { + err = b.Put(k, buf) + } + } + return err + }) + }) +} + // SessionPrune cleans up expired logs. func (d *Dao) SessionPrune() { err := d.db.Update(func(tx *bolt.Tx) (err error) { diff --git a/dao/dao.go b/dao/dao.go index 716eb65..8d2c69c 100644 --- a/dao/dao.go +++ b/dao/dao.go @@ -37,6 +37,7 @@ type Interface interface { SessionCreate(ctx context.Context, session *model.Session) error SessionUpdate(ctx context.Context, session *model.Session) error SessionUpdateExpiry(ctx context.Context, id string, expiry time.Time) (err error) + SessionUpdateDirty(ctx context.Context, userID string, roleID string) (err error) RegistryGet(ctx context.Context, id string) (*model.Registry, error) RegistryGetByURL(ctx context.Context, url string) (registry *model.Registry, err error) diff --git a/dao/mongo/session.go b/dao/mongo/session.go index 17868af..6221a7c 100644 --- a/dao/mongo/session.go +++ b/dao/mongo/session.go @@ -36,3 +36,21 @@ func (d *Dao) SessionUpdateExpiry(ctx context.Context, id string, expiry time.Ti } return d.update(ctx, Session, id, update) } + +func (d *Dao) SessionUpdateDirty(ctx context.Context, userID string, roleID string) (err error) { + filter := bson.M{} + if userID != "" { + filter["userId"] = userID + } else if roleID != "" { + filter["roles"] = roleID + } else { + return nil + } + + update := bson.M{ + "dirty": true, + "updated_by": time.Now(), + } + _, err = d.db.Collection(Session).UpdateMany(ctx, filter, update) + return +} diff --git a/docker/docker.go b/docker/docker.go index 54c6a32..a88cafa 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -4,8 +4,10 @@ import ( "context" "strings" "sync" + "time" "github.com/cuigh/auxo/app/container" + "github.com/cuigh/auxo/cache" "github.com/cuigh/auxo/errors" "github.com/cuigh/auxo/log" "github.com/cuigh/auxo/util/lazy" @@ -21,16 +23,21 @@ func newVersion(v uint64) swarm.Version { } type Docker struct { - c *client.Client - locker sync.Mutex - logger log.Logger - agents sync.Map + c *client.Client + locker sync.Mutex + logger log.Logger + nodes cache.Value + agents sync.Map + networks sync.Map } func NewDocker() *Docker { - return &Docker{ + d := &Docker{ logger: log.Get("docker"), + nodes: cache.Value{TTL: 30 * time.Minute}, } + d.nodes.Load = d.loadCache + return d } func (d *Docker) call(fn func(c *client.Client) error) error { diff --git a/docker/network.go b/docker/network.go index f1342df..a0198a9 100644 --- a/docker/network.go +++ b/docker/network.go @@ -71,16 +71,32 @@ func (d *Docker) NetworkInspect(ctx context.Context, name string) (network types // NetworkNames return network names by id list. func (d *Docker) NetworkNames(ctx context.Context, ids ...string) (names map[string]string, err error) { - var c *client.Client - if c, err = d.client(); err == nil { - names = make(map[string]string) - for _, id := range ids { - var n types.NetworkResource - n, err = c.NetworkInspect(ctx, id, types.NetworkInspectOptions{}) - if err != nil { - break + var ( + c *client.Client + network types.NetworkResource + lookup = func(id string) (n types.NetworkResource, e error) { + if c == nil { + if c, e = d.client(); e != nil { + return + } } - names[id] = n.Name + n, e = c.NetworkInspect(ctx, id, types.NetworkInspectOptions{}) + return + } + ) + + names = make(map[string]string) + for _, id := range ids { + name, ok := d.networks.Load(id) + if ok { + names[id] = name.(string) + } else { + network, err = lookup(id) + if err != nil { + return nil, err + } + names[id] = network.Name + d.networks.Store(id, network.Name) } } return diff --git a/docker/node.go b/docker/node.go index 6b23911..75a27aa 100644 --- a/docker/node.go +++ b/docker/node.go @@ -3,9 +3,7 @@ package docker import ( "context" "sort" - "time" - "github.com/cuigh/auxo/cache" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" @@ -63,13 +61,9 @@ func (d *Docker) NodeInspect(ctx context.Context, id string) (node swarm.Node, r } func (d *Docker) NodeMap() (map[string]*Node, error) { - v := cache.Value{ - TTL: 30 * time.Minute, - Load: func() (interface{}, error) { return d.loadCache() }, - } - value, err := v.Get(true) + nodes, err := d.nodes.Get(true) if err != nil { return nil, err } - return value.(map[string]*Node), nil + return nodes.(map[string]*Node), nil } diff --git a/ui/src/components/chart/Chart.vue b/ui/src/components/chart/Chart.vue index 4896a02..27ff3e5 100644 --- a/ui/src/components/chart/Chart.vue +++ b/ui/src/components/chart/Chart.vue @@ -27,32 +27,22 @@ import { NIcon, NButton, } from "naive-ui"; -import { ref, onMounted, PropType } from "vue"; +import { ref, onMounted } from "vue"; import { CloseOutline, MenuOutline } from "@vicons/ionicons5"; import { useResizeObserver } from '@vueuse/core' -import { ChartInfo } from "@/api/chart"; +import { ChartInfo } from "@/api/dashboard"; import { createChart } from "./chart"; import type { Chart } from "./chart"; -// TS: -// interface Props { -// msg?: string -// labels?: string[] -// } -// const props = withDefaults(defineProps(), { -// msg: 'hello', -// labels: () => ['one', 'two'] -// }) +interface Props { + info: ChartInfo; + data?: any; +} -const props = defineProps({ - info: { - type: Object as PropType, - required: true, - }, - data: { - type: Object, - }, -}) +// const props = withDefaults(defineProps(), { +// data: () => null, +// }) +const props = defineProps() const emits = defineEmits(['remove']) const container = ref() diff --git a/ui/src/components/chart/chart.ts b/ui/src/components/chart/chart.ts index a580e9c..35122c6 100644 --- a/ui/src/components/chart/chart.ts +++ b/ui/src/components/chart/chart.ts @@ -1,5 +1,5 @@ import * as echarts from "echarts"; -import { ChartInfo } from "@/api/chart"; +import { ChartInfo } from "@/api/dashboard"; import { store } from "@/store"; export abstract class Chart { diff --git a/ui/src/pages/service/Edit.vue b/ui/src/pages/service/Edit.vue index a8d9073..b5b52bc 100644 --- a/ui/src/pages/service/Edit.vue +++ b/ui/src/pages/service/Edit.vue @@ -43,7 +43,7 @@ - + @@ -653,22 +653,22 @@ function newFile() { } async function fetchData() { - const name = route.params.name as string || '' - if (name) { - let r = await serviceApi.find(name); + const name = route.params.name as string + + name && serviceApi.find(name, true).then(r => { model.value = r.data?.service as Service; - } + }); - let nr = await networkApi.search(); - networks.value = nr.data as Network[]; - - let cr = await configApi.search({ pageIndex: 1, pageSize: 1000 }); - configFiles.value = cr.data?.items.map(c => { + let results = await Promise.all([ + networkApi.search(), + configApi.search({ pageIndex: 1, pageSize: 1000 }), + secretApi.search({ pageIndex: 1, pageSize: 1000 }), + ]) + networks.value = results[0].data?.filter(n => n.name != 'ingress') as Network[]; + configFiles.value = results[1].data?.items.map(c => { return { label: c.name, value: `${c.id}:${c.name}` } }) - - let sr = await secretApi.search({ pageIndex: 1, pageSize: 1000 }); - secretFiles.value = sr.data?.items.map(c => { + secretFiles.value = results[2].data?.items.map(c => { return { label: c.name, value: `${c.id}:${c.name}` } }) } diff --git a/ui/src/pages/service/View.vue b/ui/src/pages/service/View.vue index 2f39cc2..2e79c78 100644 --- a/ui/src/pages/service/View.vue +++ b/ui/src/pages/service/View.vue @@ -462,7 +462,7 @@ round size="small" v-for="n in t.networks" - >{{ n.name + ": " + n.ips.join(',') }} + >{{ isEmpty(n.ips) ? n.name : (n.name + ": " + n.ips?.join(',')) }} {{ t.updatedAt }} @@ -508,7 +508,6 @@ import serviceApi from "@/api/service"; import type { Service } from "@/api/service"; import taskApi from "@/api/task"; import type { Task } from "@/api/task"; -import networkApi from "@/api/network"; import { useRoute } from "vue-router"; import { router } from "@/router/router"; import { isEmpty } from "@/utils"; @@ -703,17 +702,11 @@ async function fetchData() { let results = await Promise.all([ serviceApi.find(name, true), taskApi.search({ service: name, pageIndex: 1, pageSize: 100 }), - networkApi.search(), ]) service.value = results[0].data?.service as Service raw.value = results[0].data?.raw as string; tasks.value = results[1].data?.items as Task[]; - if (service.value.endpoint.vips && service.value.endpoint.vips.length > 0) { - let networks = new Map(); - results[2].data?.forEach(n => networks.set(n.id, n.name)) - service.value.endpoint.vips.forEach(vip => vip.name = networks.get(vip.id) as string) - } cli.value = generateCli(service.value) }