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
This commit is contained in:
xusongfu 2023-02-28 15:16:34 +08:00
parent 929842c824
commit 0a192be466
12 changed files with 289 additions and 27 deletions

View File

@ -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

View File

@ -66,6 +66,14 @@ export class ZunClient extends Base {
key: 'execute',
method: 'post',
},
{
key: 'network_attach',
method: 'post',
},
{
key: 'network_detach',
method: 'post',
},
],
subResources: [
{

View File

@ -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",

View File

@ -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": "卸载网卡",

View File

@ -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}` : '-',
},
],
},

View File

@ -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 <div key={it}>{link}</div>;
return <div key={it.id}>{link}</div>;
})
: '-'}
</>
@ -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 <div key={it.subnet}>{link}</div>;
return <div key={it.id}>{link}</div>;
})
: '-'}
</>

View File

@ -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));

View File

@ -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));

View File

@ -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,
},

View File

@ -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 <div key={it}>{link}</div>;
return <div key={it.id}>{link}</div>;
})
: '-'}
</>

View File

@ -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) => {

View File

@ -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) {