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',
|
||||
method: 'post',
|
||||
},
|
||||
{
|
||||
key: 'network_attach',
|
||||
method: 'post',
|
||||
},
|
||||
{
|
||||
key: 'network_detach',
|
||||
method: 'post',
|
||||
},
|
||||
],
|
||||
subResources: [
|
||||
{
|
||||
|
@ -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",
|
||||
|
@ -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": "卸载网卡",
|
||||
|
@ -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}` : '-',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -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>;
|
||||
})
|
||||
: '-'}
|
||||
</>
|
||||
|
@ -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 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,
|
||||
},
|
||||
|
@ -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>;
|
||||
})
|
||||
: '-'}
|
||||
</>
|
||||
|
@ -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) => {
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user