Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(console): support bypercentage for tapp gray update #992

Merged
merged 1 commit into from
Dec 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat(console): support bypercentage for tapp gray update
  • Loading branch information
tfan committed Dec 10, 2020
commit 6f1647e0d93a780924d9a7e445ea7514445c9142
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