From 0a192be466c1fe0ac0ca75f5937b23d56c83fb01 Mon Sep 17 00:00:00 2001 From: xusongfu Date: Tue, 28 Feb 2023 15:16:34 +0800 Subject: [PATCH] feature: Support attach network and detach network for zun container 1. Add attach network action for zun container 2. Add detach network action for zun container Change-Id: I0cfe22e22b1f6ba3ce525a24ca1f95e5fcb098ec --- ...rk-For-Zun-Container-e41980df3f67c5b5.yaml | 8 ++ src/client/zun/index.js | 8 ++ src/locales/en.json | 2 + src/locales/zh.json | 2 + .../Instance/actions/AttachInterface.jsx | 3 +- .../Containers/Detail/BaseDetail.jsx | 14 +-- .../Containers/actions/AttachNetwork.jsx | 87 +++++++++++++++ .../Containers/actions/DetachNetwork.jsx | 100 ++++++++++++++++++ .../containers/Containers/actions/index.jsx | 8 ++ .../containers/Containers/index.jsx | 6 +- src/resources/zun/container.js | 6 ++ src/stores/zun/containers.js | 72 ++++++++++--- 12 files changed, 289 insertions(+), 27 deletions(-) create mode 100644 releasenotes/notes/Support-Attach-Network-And-Detach-Network-For-Zun-Container-e41980df3f67c5b5.yaml create mode 100644 src/pages/container-service/containers/Containers/actions/AttachNetwork.jsx create mode 100644 src/pages/container-service/containers/Containers/actions/DetachNetwork.jsx diff --git a/releasenotes/notes/Support-Attach-Network-And-Detach-Network-For-Zun-Container-e41980df3f67c5b5.yaml b/releasenotes/notes/Support-Attach-Network-And-Detach-Network-For-Zun-Container-e41980df3f67c5b5.yaml new file mode 100644 index 00000000..26123c6a --- /dev/null +++ b/releasenotes/notes/Support-Attach-Network-And-Detach-Network-For-Zun-Container-e41980df3f67c5b5.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Support attach network and detach network for zun container + + 1. Add attach network action for zun container + + 2. Add detach network action for zun container \ No newline at end of file diff --git a/src/client/zun/index.js b/src/client/zun/index.js index 323b2cf6..be15f9e1 100644 --- a/src/client/zun/index.js +++ b/src/client/zun/index.js @@ -66,6 +66,14 @@ export class ZunClient extends Base { key: 'execute', method: 'post', }, + { + key: 'network_attach', + method: 'post', + }, + { + key: 'network_detach', + method: 'post', + }, ], subResources: [ { diff --git a/src/locales/en.json b/src/locales/en.json index f72cdc28..1f4bc279 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -164,6 +164,7 @@ "Attach": "Attach", "Attach Instance": "Attach Instance", "Attach Interface": "Attach Interface", + "Attach Network": "Attach Network", "Attach Security Group": "Attach Security Group", "Attach USB": "Attach USB", "Attach Volume": "Attach Volume", @@ -724,6 +725,7 @@ "Detach": "Detach", "Detach Instance": "Detach Instance", "Detach Interface": "Detach Interface", + "Detach Network": "Detach Network", "Detach Security Group": "Detach Security Group", "Detach Volume": "Detach Volume", "Detach interface": "Detach interface", diff --git a/src/locales/zh.json b/src/locales/zh.json index dd941433..4d5e3ff5 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -164,6 +164,7 @@ "Attach": "挂载", "Attach Instance": "绑定云主机", "Attach Interface": "挂载网卡", + "Attach Network": "绑定网络", "Attach Security Group": "绑定安全组", "Attach USB": "挂载USB", "Attach Volume": "挂载云硬盘", @@ -724,6 +725,7 @@ "Detach": "解绑", "Detach Instance": "从云主机解绑", "Detach Interface": "卸载网卡", + "Detach Network": "解绑网络", "Detach Security Group": "解绑安全组", "Detach Volume": "卸载云硬盘", "Detach interface": "卸载网卡", diff --git a/src/pages/compute/containers/Instance/actions/AttachInterface.jsx b/src/pages/compute/containers/Instance/actions/AttachInterface.jsx index dddc12e7..b6fa2ecc 100644 --- a/src/pages/compute/containers/Instance/actions/AttachInterface.jsx +++ b/src/pages/compute/containers/Instance/actions/AttachInterface.jsx @@ -195,7 +195,8 @@ export class AttachInterface extends ModalAction { { title: t('Allocation Pools'), dataIndex: 'allocation_pools', - render: (value) => `${value[0].start} -- ${value[0].end}`, + render: (value) => + value.length ? `${value[0].start} -- ${value[0].end}` : '-', }, ], }, diff --git a/src/pages/container-service/containers/Containers/Detail/BaseDetail.jsx b/src/pages/container-service/containers/Containers/Detail/BaseDetail.jsx index f4ac8784..78beeaeb 100644 --- a/src/pages/container-service/containers/Containers/Detail/BaseDetail.jsx +++ b/src/pages/container-service/containers/Containers/Detail/BaseDetail.jsx @@ -224,10 +224,10 @@ export class BaseDetail extends Base { <> {value.length ? value.map((it) => { - const link = this.getLinkRender('networkDetail', it, { - id: it, + const link = this.getLinkRender('networkDetail', it.name, { + id: it.id, }); - return
{link}
; + return
{link}
; }) : '-'} @@ -240,11 +240,11 @@ export class BaseDetail extends Base { <> {value.length ? value.map((it) => { - const link = this.getLinkRender('subnetDetail', it.subnet, { - networkId: it.network, - id: it.subnet, + const link = this.getLinkRender('subnetDetail', it.name, { + networkId: it.network_id, + id: it.id, }); - return
{link}
; + return
{link}
; }) : '-'} diff --git a/src/pages/container-service/containers/Containers/actions/AttachNetwork.jsx b/src/pages/container-service/containers/Containers/actions/AttachNetwork.jsx new file mode 100644 index 00000000..7aa30945 --- /dev/null +++ b/src/pages/container-service/containers/Containers/actions/AttachNetwork.jsx @@ -0,0 +1,87 @@ +// Copyright 2021 99cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { inject, observer } from 'mobx-react'; +import globalContainersStore from 'src/stores/zun/containers'; +import { ModalAction } from 'containers/Action'; +import { checkItemAction } from 'resources/zun/container'; + +export class AttachNetwork extends ModalAction { + static id = 'AttachNetwork'; + + static title = t('Attach Network'); + + init() { + this.store = globalContainersStore; + } + + static get modalSize() { + return 'large'; + } + + getModalSize() { + return 'large'; + } + + get name() { + return t('Attach Network'); + } + + get defaultValue() { + const { name } = this.item; + const value = { + instance: name, + }; + return value; + } + + static policy = 'container:network_attach'; + + aliasPolicy = 'zun:container:network_attach'; + + static allowed = (item) => { + return checkItemAction(item, 'network_attach_detach'); + }; + + disabledNetwork = (it) => { + const { networks } = this.item; + return networks.some((net) => net.id === it.id); + }; + + get formItems() { + return [ + { + name: 'instance', + label: t('Instance'), + type: 'label', + iconType: 'instance', + }, + { + name: 'networks', + label: t('Networks'), + type: 'network-select-table', + required: true, + disabledFunc: this.disabledNetwork, + }, + ]; + } + + onSubmit = (values) => { + const { networks } = values; + const network = networks.selectedRowKeys[0]; + return this.store.attachNetwork(this.item.id, { network }); + }; +} + +export default inject('rootStore')(observer(AttachNetwork)); diff --git a/src/pages/container-service/containers/Containers/actions/DetachNetwork.jsx b/src/pages/container-service/containers/Containers/actions/DetachNetwork.jsx new file mode 100644 index 00000000..c95b9b78 --- /dev/null +++ b/src/pages/container-service/containers/Containers/actions/DetachNetwork.jsx @@ -0,0 +1,100 @@ +// Copyright 2021 99cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { inject, observer } from 'mobx-react'; +import globalContainersStore from 'src/stores/zun/containers'; +import { ModalAction } from 'containers/Action'; +import { checkItemAction } from 'resources/zun/container'; +import { networkColumns } from 'resources/neutron/network'; + +export class DetachNetwork extends ModalAction { + static id = 'DetachNetwork'; + + static title = t('Detach Network'); + + init() { + this.store = globalContainersStore; + } + + static get modalSize() { + return 'large'; + } + + getModalSize() { + return 'large'; + } + + get name() { + return t('Detach Network'); + } + + get networks() { + const { networks = [] } = this.item; + return networks; + } + + get defaultValue() { + const { name } = this.item; + const value = { + instance: name, + }; + return value; + } + + static policy = 'container:network_detach'; + + aliasPolicy = 'zun:container:network_detach'; + + static allowed = (item) => { + return checkItemAction(item, 'network_attach_detach'); + }; + + disabledNetwork = (it) => { + const { networks } = this.item; + return networks.includes(it.id); + }; + + get formItems() { + return [ + { + name: 'instance', + label: t('Instance'), + type: 'label', + iconType: 'instance', + }, + { + name: 'networks', + label: t('Networks'), + type: 'select-table', + data: this.networks, + columns: networkColumns(this), + filterParams: [ + { + label: t('Name'), + name: 'name', + }, + ], + required: true, + }, + ]; + } + + onSubmit = (values) => { + const { networks } = values; + const network = networks.selectedRowKeys[0]; + return this.store.detachNetwork(this.item.id, { network }); + }; +} + +export default inject('rootStore')(observer(DetachNetwork)); diff --git a/src/pages/container-service/containers/Containers/actions/index.jsx b/src/pages/container-service/containers/Containers/actions/index.jsx index 117c4f43..ce7cc678 100644 --- a/src/pages/container-service/containers/Containers/actions/index.jsx +++ b/src/pages/container-service/containers/Containers/actions/index.jsx @@ -23,6 +23,8 @@ import EditContainer from './Edit'; import KillContainer from './Kill'; import ForceDeleteContainer from './ForceDelete'; import ExecuteCommandContainer from './ExecuteCommand'; +import AttachNetwork from './AttachNetwork'; +import DetachNetwork from './DetachNetwork'; const statusActions = [ StartContainer, @@ -32,6 +34,8 @@ const statusActions = [ RebuildContainer, ]; +const resourceActions = [AttachNetwork, DetachNetwork]; + const actionConfigs = { rowActions: { firstAction: DeleteContainer, @@ -45,6 +49,10 @@ const actionConfigs = { ExecuteCommandContainer, ], }, + { + title: t('Related Resources'), + actions: resourceActions, + }, { action: EditContainer, }, diff --git a/src/pages/container-service/containers/Containers/index.jsx b/src/pages/container-service/containers/Containers/index.jsx index 0ee6da6b..e9279206 100644 --- a/src/pages/container-service/containers/Containers/index.jsx +++ b/src/pages/container-service/containers/Containers/index.jsx @@ -97,10 +97,10 @@ export class Containers extends Base { <> {value.length ? value.map((it) => { - const link = this.getLinkRender('networkDetail', it, { - id: it, + const link = this.getLinkRender('networkDetail', it.name, { + id: it.id, }); - return
{link}
; + return
{link}
; }) : '-'} diff --git a/src/resources/zun/container.js b/src/resources/zun/container.js index 99a6dab8..fed89ca4 100644 --- a/src/resources/zun/container.js +++ b/src/resources/zun/container.js @@ -103,6 +103,12 @@ const validStates = { states.STOPPED, states.PAUSED, ], + network_attach_detach: [ + states.CREATED, + states.RUNNING, + states.STOPPED, + states.PAUSED, + ], }; export const checkItemAction = (item, actionName) => { diff --git a/src/stores/zun/containers.js b/src/stores/zun/containers.js index a6de3c7e..de9dc1b6 100644 --- a/src/stores/zun/containers.js +++ b/src/stores/zun/containers.js @@ -25,26 +25,20 @@ export class ContainersStore extends Base { return client.glance.images; } + get networkClient() { + return client.neutron.networks; + } + + get subnetClient() { + return client.neutron.subnets; + } + get mapper() { return (data) => { - const { addresses = {} } = data; - const networks = Object.keys(addresses); - const addrs = []; - const subnets = []; - Object.entries(addresses).forEach(([key, val]) => { - (val || []).forEach((v) => { - addrs.push({ network: key, addr: v.addr }); - subnets.push({ network: key, subnet: v.subnet_id }); - }); - }); - return { ...data, id: data.uuid, task_state: data.task_state === null ? 'free' : data.task_state, - networks, - addrs, - subnets, }; }; } @@ -104,8 +98,40 @@ export class ContainersStore extends Base { return this.client.execute(id, data); } + @action + async attachNetwork(id, data) { + return this.client.network_attach(id, null, data); + } + + @action + async detachNetwork(id, data) { + return this.client.network_detach(id, null, data); + } + + async listDidFetch(items) { + if (!items.length) return items; + const [{ networks: allNetworks }, { subnets: allSubnets }] = + await Promise.all([this.networkClient.list(), this.subnetClient.list()]); + return items.map((it) => { + const { addresses = {} } = it; + const networks = []; + const addrs = []; + const subnets = []; + Object.entries(addresses).forEach(([key, val]) => { + (val || []).forEach((v) => { + const network = allNetworks.find((net) => net.id === key); + const subnet = allSubnets.find((sub) => sub.id === v.subnet_id); + addrs.push({ network, addr: v.addr, port: v.port }); + networks.push(network); + subnets.push(subnet); + }); + }); + return { ...it, addrs, networks, subnets }; + }); + } + async detailDidFetch(item) { - const { uuid, status, image_driver, image } = item; + const { uuid, status, image_driver, image, addresses = {} } = item; let stats = {}; if (status === 'Running') { stats = (await this.client.stats.list(uuid)) || {}; @@ -116,7 +142,21 @@ export class ContainersStore extends Base { item.imageInfo = info; } catch (error) {} } - return { ...item, stats }; + const [{ networks: allNetworks }, { subnets: allSubnets }] = + await Promise.all([this.networkClient.list(), this.subnetClient.list()]); + const networks = []; + const addrs = []; + const subnets = []; + Object.entries(addresses).forEach(([key, val]) => { + (val || []).forEach((v) => { + const network = allNetworks.find((net) => net.id === key); + const subnet = allSubnets.find((sub) => sub.id === v.subnet_id); + addrs.push({ network, addr: v.addr, port: v.port }); + networks.push(network); + subnets.push(subnet); + }); + }); + return { ...item, stats, networks, addrs, subnets }; } async fetchLogs(id) {