Skip to content

Commit

Permalink
feat(console): support bypercentage for tapp gray update (tkestack#992)
Browse files Browse the repository at this point in the history
  • Loading branch information
tfan committed Dec 10, 2020
1 parent eac402f commit afdf549
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 135 deletions.
14 changes: 8 additions & 6 deletions web/console/src/modules/cluster/actions/resourcePodActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,25 +162,27 @@ const restActions = {
},

/**Tapp灰度升级编辑项 */
initTappGrayUpdate: (items: TappGrayUpdateEditItem[]): ReduxAction<TappGrayUpdateEditItem[]> => {
initTappGrayUpdate: (setting: TappGrayUpdateEditItem): ReduxAction<TappGrayUpdateEditItem> => {
return {
type: ActionType.W_TappGrayUpdate,
payload: items
payload: setting
};
},
updateTappGrayUpdate: (index_out, index_in, imageName, imageTag) => {

updateTappGrayUpdate: (index_in, imageName, imageTag) => {
return async (dispatch, getState: GetState) => {
const { editTappGrayUpdate } = getState().subRoot.resourceDetailState;
const target: TappGrayUpdateEditItem[] = cloneDeep(editTappGrayUpdate);
target[index_out].containers[index_in].imageName = imageName;
target[index_out].containers[index_in].imageTag = imageTag;
const target: TappGrayUpdateEditItem = cloneDeep(editTappGrayUpdate);
target.containers[index_in].imageName = imageName;
target.containers[index_in].imageTag = imageTag;

dispatch({
type: ActionType.W_TappGrayUpdate,
payload: target
});
};
},

/** 是否展示 登录弹框 */
toggleLoginDialog: () => {
return async (dispatch, getState: GetState) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -792,11 +792,11 @@ export const validateWorkloadActions = {
};
},

validateGrayUpdateRegistrySelection(index_out: number, index_in: number, imageName: string) {
validateGrayUpdateRegistrySelection(index_in: number, imageName: string) {
return async (dispatch, getState: GetState) => {
const { editTappGrayUpdate } = getState().subRoot.resourceDetailState;
const target: TappGrayUpdateEditItem[] = cloneDeep(editTappGrayUpdate);
target[index_out].containers[index_in].v_imageName = validateWorkloadActions._validateRegistrySelection(
const target: TappGrayUpdateEditItem = cloneDeep(editTappGrayUpdate);
target.containers[index_in].v_imageName = validateWorkloadActions._validateRegistrySelection(
imageName
);

Expand All @@ -810,12 +810,10 @@ export const validateWorkloadActions = {
validateGrayUpdate() {
return async (dispatch, getState: GetState) => {
const { editTappGrayUpdate } = getState().subRoot.resourceDetailState;
editTappGrayUpdate.forEach((item, index_out) => {
item.containers.forEach((container, index_in) => {
dispatch(
validateWorkloadActions.validateGrayUpdateRegistrySelection(index_out, index_in, container.imageName)
);
});
editTappGrayUpdate.containers.forEach((container, index_in) => {
dispatch(
validateWorkloadActions.validateGrayUpdateRegistrySelection(index_in, container.imageName)
);
});
};
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,133 @@ import * as classnames from 'classnames';
import * as React from 'react';
import { connect } from 'react-redux';

import { Alert, Bubble, Button, Input, Modal, Text } from '@tea/component';
import { Alert, Bubble, Button, Input, Modal, Text, Form, Radio, InputNumber } from '@tea/component';
import { FormPanel } from '@tencent/ff-component';
import { bindActionCreators, uuid } from '@tencent/ff-redux';
import { t, Trans } from '@tencent/tea-app/lib/i18n';

import { resourceConfig } from '../../../../../../config';
import { cloneDeep, CreateResource, InputField, WorkflowDialog, TipInfo } from '../../../../common';
import { cloneDeep, CreateResource, InputField, WorkflowDialog, TipInfo, initValidator } from '../../../../common';
import { allActions } from '../../../actions/';
import { RootProps } from '../../ClusterApp';
import { Pod } from '../../../models';

const mapDispatchToProps = dispatch =>
Object.assign({}, bindActionCreators({ actions: allActions }, dispatch), { dispatch });

enum UpdateType {
UserSelect = 'UserSelect', // 升级用户选中的pod
ByPercentage = 'ByPercentage' // 按比例升级pod
}

@connect(state => state, mapDispatchToProps)
export class ResourceGrayUpgradeDialog extends React.Component<RootProps, {}> {
state = {
updateType: UpdateType.UserSelect,
updatePercentage: 20
};

// 升级比例的自动步进
get step(): number {
return 10;
}

// 升级类型
get updateType(): UpdateType {
return this.state.updateType;
}

setUpdateType = (updateType: string): void => {
this.setState({ updateType });
};

// 升级比例
get updatePercentage(): number {
return this.state.updatePercentage;
}

setUpdatePercentage = (updatePercentage: number): void => {
this.setState({ updatePercentage });
};

// 用户选中的pods记录
get podSelection(): Pod[] {
return this.props.subRoot.resourceDetailState.podSelection;
}

// 全部pods记录
get podRecords(): Pod[] {
return this.props.subRoot.resourceDetailState.podList.data.records;
}

// 要升级的pods记录
get targetPods(): Pod[] {
let result = [];
switch (this.updateType) {
case UpdateType.UserSelect:
result = this.podSelection;
break;
case UpdateType.ByPercentage:
if (this.updatePercentage > 0) {
// 取前x%
result = this.podRecords.slice(0, Math.floor((this.podRecords.length * this.updatePercentage) / 100));
} else {
// 取后x%
result = this.podRecords.slice(Math.floor((this.podRecords.length * (this.updatePercentage + 100)) / 100));
}
break;
default:
break;
}
return result;
}

render() {
const { actions, region, clusterVersion, route } = this.props;
const { updateGrayTappFlow, editTappGrayUpdate } = this.props.subRoot.resourceDetailState;
const { updateGrayTappFlow, editTappGrayUpdate, podList, podSelection } = this.props.subRoot.resourceDetailState;
const targetResource = this.props.subRoot.resourceOption.ffResourceList.selection;
let resourceInfo = resourceConfig(clusterVersion)['tapp'];
let templates = {};
let templatePool = {};
editTappGrayUpdate.forEach(item => {
let indexName = item.name.split(item.generateName)[1]; //获取第几个实例
if (indexName) {
templates[indexName] = item.name;
}
//将spec.template复制过来,然后修改caontainer里的name和imageName字段
let newTemplate = cloneDeep(targetResource.spec.template);

//editTapp只存储了必要的修改信息如containerName和containerImg,
//所以更新的时候需要把container的之前的内容也带上,不然之前的内容会被清空
newTemplate.spec.containers = item.containers.map(container => {
let targetContainer = targetResource.spec.template.spec.containers.find(c => c.name === container.name);
return {
...targetContainer,
name: container.name,
image: `${container.imageName}:${container.imageTag}`
};

function getJsonData(pods, updateSettings) {
let templates = {};
let templatePool = {};
pods.forEach(pod => {
let indexName = pod.metadata.name.split(pod.metadata.generateName)[1]; //获取第几个实例
if (indexName) {
templates[indexName] = pod.metadata.name;
}
//将spec.template复制过来,然后修改caontainer里的name和imageName字段
let newTemplate = cloneDeep(targetResource.spec.template);

//editTapp只存储了必要的修改信息如containerName和containerImg,
//所以更新的时候需要把container的之前的内容也带上,不然之前的内容会被清空
newTemplate.spec.containers = pod.spec.containers.map(container => {
let targetContainer = targetResource.spec.template.spec.containers.find(c => c.name === container.name);
let containerSetting = updateSettings.containers.find(c => c.name === container.name);
return {
...targetContainer,
image: `${containerSetting.imageName}:${containerSetting.imageTag}`
};
});
templatePool[pod.metadata.name] = newTemplate;
});
templatePool[item.name] = newTemplate;
});

let jsonYaml = {
spec: {
templatePool,
templates
}
};
let jsonYaml = {
spec: {
templatePool,
templates
}
};
return jsonYaml;
}

// 需要提交的数据
let resource: CreateResource = {
id: uuid(),
resourceInfo: resourceInfo,
namespace: route.queries['np'],
clusterId: route.queries['clusterId'],
resourceIns: route.queries['resourceIns'],
jsonData: JSON.stringify(jsonYaml),
isStrategic: false
};

Expand All @@ -72,46 +142,70 @@ export class ResourceGrayUpgradeDialog extends React.Component<RootProps, {}> {
targets={[resource]}
validateAction={this._validation.bind(this)}
preAction={() => {
const setResourceJSONData = resource => {
let jsonYaml = {};
jsonYaml = getJsonData(this.targetPods, editTappGrayUpdate);
resource.jsonData = JSON.stringify(jsonYaml);
};
setResourceJSONData(resource);
actions.resourceDetail.pod.podSelect([]);
}}
>
<div style={{ maxHeight: '700px', overflowY: 'auto' }}>
<TipInfo type="info">{t('灰度升级过程中,下列所有容器实例会同时重启')}</TipInfo>

<FormPanel isNeedCard={false} style={{ paddingRight: '80px' }}>
{editTappGrayUpdate.map((item, index_out) => (
<FormPanel.Item
key={index_out}
label={t(`容器实例 - ${item.name.split(item.generateName)[1]}`)}
labelStyle={{ textAlign: 'center' }}
>
{item.containers.map((container, index_in) => {
<FormPanel isNeedCard={false}>
<Form.Item label="升级方式">
<Radio.Group value={this.updateType} onChange={value => this.setUpdateType(value)}>
<Radio name={UpdateType.UserSelect}>升级选中POD</Radio>
<Radio name={UpdateType.ByPercentage}>按比例升级POD</Radio>
</Radio.Group>
</Form.Item>
{this.updateType === UpdateType.ByPercentage && (
<Form.Item label="升级比例">
<InputNumber
min={-100}
max={100}
value={this.updatePercentage}
step={this.step}
onChange={this.setUpdatePercentage}
/>
</Form.Item>
)}
<Form.Item label="待升级POD">
<Form.Text>
{this.targetPods.length > 0 ? `共${this.targetPods.length}个` : '-'}
<ul>
{this.targetPods.slice(0, 3).map(item => (
<li>{item.metadata.name}</li>
))}
{this.targetPods.length > 3 && (
<li>
<Bubble content={this.targetPods.map(item => item.metadata.name).join(', ')}>...</Bubble>
</li>
)}
</ul>
</Form.Text>
</Form.Item>
{editTappGrayUpdate && (
<Form.Item label="镜像设置">
{editTappGrayUpdate.containers.map((container, index_in) => {
return (
<FormPanel
isNeedCard={false}
fixed
style={{ padding: '35px', marginBottom: '20px' }}
key={index_in}
>
<FormPanel.Item label={t('名称')} text>
<span className="text-label">{container.name}</span>
</FormPanel.Item>
<FormPanel isNeedCard={false} fixed style={{ marginBottom: '20px' }} key={index_in}>
<FormPanel.Item label={t('镜像')}>
<div className={classnames('form-unit', { 'is-error': container.v_imageName.status === 2 })}>
<Bubble content={container.v_imageName.status === 2 ? container.v_imageName.message : null}>
<Input
size="full"
value={container.imageName}
onChange={value => {
actions.resourceDetail.pod.updateTappGrayUpdate(
index_out,
index_in,
value,
container.imageTag
);
}}
onBlur={e => {
actions.validate.workload.validateGrayUpdateRegistrySelection(
index_out,
index_in,
e.target.value
);
Expand All @@ -122,10 +216,10 @@ export class ResourceGrayUpgradeDialog extends React.Component<RootProps, {}> {
</FormPanel.Item>
<FormPanel.Item label={t('镜像版本(TAG)')}>
<Input
size="full"
value={container.imageTag}
onChange={value => {
actions.resourceDetail.pod.updateTappGrayUpdate(
index_out,
index_in,
container.imageName,
value
Expand All @@ -136,8 +230,8 @@ export class ResourceGrayUpgradeDialog extends React.Component<RootProps, {}> {
</FormPanel>
);
})}
</FormPanel.Item>
))}
</Form.Item>
)}
</FormPanel>
</div>
</WorkflowDialog>
Expand All @@ -146,13 +240,10 @@ export class ResourceGrayUpgradeDialog extends React.Component<RootProps, {}> {

/** 校验Tapp 灰度升级 是否正确 */
private async _validation() {
await this.props.actions.validator.workload.validateGrayUpdate();
let result = true;
let { editTappGrayUpdate } = this.props.subRoot.resourceDetailState;
editTappGrayUpdate.forEach(item => {
item.containers.forEach(container => {
result = result && container.v_imageName.status === 1;
});
editTappGrayUpdate.containers.forEach(container => {
result = result && container.v_imageName.status === 1;
});
return result;
}
Expand Down
Loading

0 comments on commit afdf549

Please sign in to comment.