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:
parent
929842c824
commit
0a192be466
@ -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
|
@ -66,6 +66,14 @@ export class ZunClient extends Base {
|
|||||||
key: 'execute',
|
key: 'execute',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'network_attach',
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'network_detach',
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
subResources: [
|
subResources: [
|
||||||
{
|
{
|
||||||
|
@ -164,6 +164,7 @@
|
|||||||
"Attach": "Attach",
|
"Attach": "Attach",
|
||||||
"Attach Instance": "Attach Instance",
|
"Attach Instance": "Attach Instance",
|
||||||
"Attach Interface": "Attach Interface",
|
"Attach Interface": "Attach Interface",
|
||||||
|
"Attach Network": "Attach Network",
|
||||||
"Attach Security Group": "Attach Security Group",
|
"Attach Security Group": "Attach Security Group",
|
||||||
"Attach USB": "Attach USB",
|
"Attach USB": "Attach USB",
|
||||||
"Attach Volume": "Attach Volume",
|
"Attach Volume": "Attach Volume",
|
||||||
@ -724,6 +725,7 @@
|
|||||||
"Detach": "Detach",
|
"Detach": "Detach",
|
||||||
"Detach Instance": "Detach Instance",
|
"Detach Instance": "Detach Instance",
|
||||||
"Detach Interface": "Detach Interface",
|
"Detach Interface": "Detach Interface",
|
||||||
|
"Detach Network": "Detach Network",
|
||||||
"Detach Security Group": "Detach Security Group",
|
"Detach Security Group": "Detach Security Group",
|
||||||
"Detach Volume": "Detach Volume",
|
"Detach Volume": "Detach Volume",
|
||||||
"Detach interface": "Detach interface",
|
"Detach interface": "Detach interface",
|
||||||
|
@ -164,6 +164,7 @@
|
|||||||
"Attach": "挂载",
|
"Attach": "挂载",
|
||||||
"Attach Instance": "绑定云主机",
|
"Attach Instance": "绑定云主机",
|
||||||
"Attach Interface": "挂载网卡",
|
"Attach Interface": "挂载网卡",
|
||||||
|
"Attach Network": "绑定网络",
|
||||||
"Attach Security Group": "绑定安全组",
|
"Attach Security Group": "绑定安全组",
|
||||||
"Attach USB": "挂载USB",
|
"Attach USB": "挂载USB",
|
||||||
"Attach Volume": "挂载云硬盘",
|
"Attach Volume": "挂载云硬盘",
|
||||||
@ -724,6 +725,7 @@
|
|||||||
"Detach": "解绑",
|
"Detach": "解绑",
|
||||||
"Detach Instance": "从云主机解绑",
|
"Detach Instance": "从云主机解绑",
|
||||||
"Detach Interface": "卸载网卡",
|
"Detach Interface": "卸载网卡",
|
||||||
|
"Detach Network": "解绑网络",
|
||||||
"Detach Security Group": "解绑安全组",
|
"Detach Security Group": "解绑安全组",
|
||||||
"Detach Volume": "卸载云硬盘",
|
"Detach Volume": "卸载云硬盘",
|
||||||
"Detach interface": "卸载网卡",
|
"Detach interface": "卸载网卡",
|
||||||
|
@ -195,7 +195,8 @@ export class AttachInterface extends ModalAction {
|
|||||||
{
|
{
|
||||||
title: t('Allocation Pools'),
|
title: t('Allocation Pools'),
|
||||||
dataIndex: 'allocation_pools',
|
dataIndex: 'allocation_pools',
|
||||||
render: (value) => `${value[0].start} -- ${value[0].end}`,
|
render: (value) =>
|
||||||
|
value.length ? `${value[0].start} -- ${value[0].end}` : '-',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -224,10 +224,10 @@ export class BaseDetail extends Base {
|
|||||||
<>
|
<>
|
||||||
{value.length
|
{value.length
|
||||||
? value.map((it) => {
|
? value.map((it) => {
|
||||||
const link = this.getLinkRender('networkDetail', it, {
|
const link = this.getLinkRender('networkDetail', it.name, {
|
||||||
id: it,
|
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.length
|
||||||
? value.map((it) => {
|
? value.map((it) => {
|
||||||
const link = this.getLinkRender('subnetDetail', it.subnet, {
|
const link = this.getLinkRender('subnetDetail', it.name, {
|
||||||
networkId: it.network,
|
networkId: it.network_id,
|
||||||
id: it.subnet,
|
id: it.id,
|
||||||
});
|
});
|
||||||
return <div key={it.subnet}>{link}</div>;
|
return <div key={it.id}>{link}</div>;
|
||||||
})
|
})
|
||||||
: '-'}
|
: '-'}
|
||||||
</>
|
</>
|
||||||
|
@ -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));
|
@ -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));
|
@ -23,6 +23,8 @@ import EditContainer from './Edit';
|
|||||||
import KillContainer from './Kill';
|
import KillContainer from './Kill';
|
||||||
import ForceDeleteContainer from './ForceDelete';
|
import ForceDeleteContainer from './ForceDelete';
|
||||||
import ExecuteCommandContainer from './ExecuteCommand';
|
import ExecuteCommandContainer from './ExecuteCommand';
|
||||||
|
import AttachNetwork from './AttachNetwork';
|
||||||
|
import DetachNetwork from './DetachNetwork';
|
||||||
|
|
||||||
const statusActions = [
|
const statusActions = [
|
||||||
StartContainer,
|
StartContainer,
|
||||||
@ -32,6 +34,8 @@ const statusActions = [
|
|||||||
RebuildContainer,
|
RebuildContainer,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const resourceActions = [AttachNetwork, DetachNetwork];
|
||||||
|
|
||||||
const actionConfigs = {
|
const actionConfigs = {
|
||||||
rowActions: {
|
rowActions: {
|
||||||
firstAction: DeleteContainer,
|
firstAction: DeleteContainer,
|
||||||
@ -45,6 +49,10 @@ const actionConfigs = {
|
|||||||
ExecuteCommandContainer,
|
ExecuteCommandContainer,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('Related Resources'),
|
||||||
|
actions: resourceActions,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
action: EditContainer,
|
action: EditContainer,
|
||||||
},
|
},
|
||||||
|
@ -97,10 +97,10 @@ export class Containers extends Base {
|
|||||||
<>
|
<>
|
||||||
{value.length
|
{value.length
|
||||||
? value.map((it) => {
|
? value.map((it) => {
|
||||||
const link = this.getLinkRender('networkDetail', it, {
|
const link = this.getLinkRender('networkDetail', it.name, {
|
||||||
id: it,
|
id: it.id,
|
||||||
});
|
});
|
||||||
return <div key={it}>{link}</div>;
|
return <div key={it.id}>{link}</div>;
|
||||||
})
|
})
|
||||||
: '-'}
|
: '-'}
|
||||||
</>
|
</>
|
||||||
|
@ -103,6 +103,12 @@ const validStates = {
|
|||||||
states.STOPPED,
|
states.STOPPED,
|
||||||
states.PAUSED,
|
states.PAUSED,
|
||||||
],
|
],
|
||||||
|
network_attach_detach: [
|
||||||
|
states.CREATED,
|
||||||
|
states.RUNNING,
|
||||||
|
states.STOPPED,
|
||||||
|
states.PAUSED,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkItemAction = (item, actionName) => {
|
export const checkItemAction = (item, actionName) => {
|
||||||
|
@ -25,26 +25,20 @@ export class ContainersStore extends Base {
|
|||||||
return client.glance.images;
|
return client.glance.images;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get networkClient() {
|
||||||
|
return client.neutron.networks;
|
||||||
|
}
|
||||||
|
|
||||||
|
get subnetClient() {
|
||||||
|
return client.neutron.subnets;
|
||||||
|
}
|
||||||
|
|
||||||
get mapper() {
|
get mapper() {
|
||||||
return (data) => {
|
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 {
|
return {
|
||||||
...data,
|
...data,
|
||||||
id: data.uuid,
|
id: data.uuid,
|
||||||
task_state: data.task_state === null ? 'free' : data.task_state,
|
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);
|
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) {
|
async detailDidFetch(item) {
|
||||||
const { uuid, status, image_driver, image } = item;
|
const { uuid, status, image_driver, image, addresses = {} } = item;
|
||||||
let stats = {};
|
let stats = {};
|
||||||
if (status === 'Running') {
|
if (status === 'Running') {
|
||||||
stats = (await this.client.stats.list(uuid)) || {};
|
stats = (await this.client.stats.list(uuid)) || {};
|
||||||
@ -116,7 +142,21 @@ export class ContainersStore extends Base {
|
|||||||
item.imageInfo = info;
|
item.imageInfo = info;
|
||||||
} catch (error) {}
|
} 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) {
|
async fetchLogs(id) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user