Separate provisioning of nodes in Fuel UI

Separate provisioning task can be started
for all environment nodes in Fuel UI

Implements: blueprint allow-choosing-nodes-for-provisioning-and-deployment

Closes-Bug: #1522444

Change-Id: I30b902da2f6134d0b229f23aa99c7e8c7a362873
This commit is contained in:
Julia Aranovich 2016-02-01 12:18:43 +03:00
parent 955b3b6c15
commit 99946e3722
9 changed files with 940 additions and 602 deletions

View File

@ -363,10 +363,9 @@ models.Cluster = BaseModel.extend({
return !this.get('is_locked');
},
isDeploymentPossible() {
var nodes = this.get('nodes');
return this.get('release').get('state') !== 'unavailable' && !!nodes.length &&
(nodes.hasChanges() || this.needsRedeployment()) &&
!this.task({group: 'deployment', active: true});
return this.get('release').get('state') !== 'unavailable' &&
!this.task({group: 'deployment', active: true}) &&
(this.get('status') !== 'operational' || this.get('nodes').hasChanges());
},
getCapacity() {
var result = {
@ -458,10 +457,14 @@ models.Node = BaseModel.extend({
if (!onlyDeployedRoles) nodeRoles = nodeRoles.concat(this.get('pending_roles'));
return !!_.intersection(nodeRoles, roles).length;
},
isProvisioningPossible() {
var status = this.get('status');
return status === 'discover' || status === 'error' && this.get('error_type') === 'provisioning';
},
hasChanges() {
return this.get('pending_addition') ||
this.get('pending_deletion') ||
this.get('cluster') && !!this.get('pending_roles').length;
!!this.get('cluster') && !!this.get('pending_roles').length;
},
areDisksConfigurable() {
var status = this.get('status');
@ -576,7 +579,9 @@ models.Task = BaseModel.extend({
},
groups: {
network: ['verify_networks', 'check_networks'],
deployment: ['update', 'stop_deployment', 'deploy', 'reset_environment', 'spawn_vms']
deployment: [
'update', 'stop_deployment', 'deploy', 'provision', 'reset_environment', 'spawn_vms'
]
},
extendGroups(filters) {
var names = utils.composeList(filters.name);
@ -628,11 +633,13 @@ models.Tasks = BaseCollection.extend({
},
comparator: 'id',
filterTasks(filters) {
return _.flatten(_.map(this.model.prototype.extendGroups(filters), (name) => {
return this.filter((task) => {
return task.match(_.extend(_.omit(filters, 'group'), {name: name}));
});
}));
return _.chain(this.model.prototype.extendGroups(filters))
.map((name) => {
return this.filter((task) => task.match(_.extend(_.omit(filters, 'group'), {name: name})));
})
.flatten()
.compact()
.value();
},
findTask(filters) {
return this.filterTasks(filters)[0];

View File

@ -3218,7 +3218,7 @@ input[type=range] {
}
}
.display-changes-dialog {
.display-changes-dialog, .provision-nodes-dialog {
hr {
margin: 8px 0 12px 0;
}
@ -4372,7 +4372,7 @@ input[type=range] {
margin-bottom: 6px;
.capacity-items {
margin-top: -9px;
.capacity-item {
> div {
cursor: default;
background-color: @white;
padding: 4px 10px;
@ -4388,6 +4388,7 @@ input[type=range] {
}
.capacity-value {
.font-semibold;
float: right;
}
}
}
@ -4411,9 +4412,11 @@ input[type=range] {
}
}
@dashboard-border-color: #d3d3d3;
.dashboard-block {
padding: @dashboard-offset;
border: 1px solid #d3d3d3;
border: 1px solid @dashboard-border-color;
border-radius: 4px;
margin: 0 15px @dashboard-offset * 2;
> div {
@ -4463,7 +4466,7 @@ input[type=range] {
.instruction {
margin-bottom: @dashboard-offset;
}
.environment-alerts {
.task-alerts {
.invalid {
.font-semibold;
font-size: @base-font-size - 1;
@ -4498,7 +4501,21 @@ input[type=range] {
width: 95%;
}
}
&.actions-panel {
.no-nodes {
color: @gray;
}
.nav {
margin-right: 0;
li.deployment-modes-label {
color: @gray;
padding: 7px 0 0;
cursor: default;
}
}
}
}
.links-block {
.row:not(:last-child) {
margin-bottom: @dashboard-offset * 2;

View File

@ -22,7 +22,7 @@ define([
function DashboardPage(remote) {
this.remote = remote;
this.modal = new ModalWindow(remote);
this.deployButtonSelector = 'button.deploy-btn';
this.deployButtonSelector = '.actions-panel .deploy-btn';
}
DashboardPage.prototype = {

View File

@ -68,8 +68,11 @@ define([
.type('\uE00C')
.end()
.assertElementNotExists(renameInputSelector, 'Rename control disappears')
.assertElementTextEquals(nameSelector, initialName,
'Switching rename control does not change cluster name')
.assertElementTextEquals(
nameSelector,
initialName,
'Switching rename control does not change cluster name'
)
.then(function() {
return dashboardPage.setClusterName(newName);
})
@ -86,9 +89,13 @@ define([
.then(function() {
return dashboardPage.setClusterName(initialName);
})
.assertElementAppears('.rename-block.has-error', 1000,
'Error style for duplicate name is applied')
.assertElementTextEquals('.rename-block .text-danger',
.assertElementAppears(
'.rename-block.has-error',
1000,
'Error style for duplicate name is applied'
)
.assertElementTextEquals(
'.rename-block .text-danger',
'Environment with this name already exists',
'Duplicate name error text appears'
)
@ -101,7 +108,7 @@ define([
return clustersPage.goToEnvironment(initialName);
});
},
'Provision button availability': function() {
'Provision VMs button availability': function() {
return this.remote
.then(function() {
return common.addNodesToCluster(1, ['Virtual']);
@ -109,8 +116,13 @@ define([
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.assertElementTextEquals(dashboardPage.deployButtonSelector, 'Provision VMs',
'After adding Virtual node deploy button has appropriate text')
.assertElementAppears(
'.actions-panel .btn-provision-vms',
1000,
'Provision VMs action appears on the Dashboard'
)
.clickByCssSelector('.actions-panel .nav button.dropdown-toggle')
.clickByCssSelector('.actions-panel .nav .dropdown-menu li.deploy button')
.then(function() {
return dashboardPage.discardChanges();
});
@ -124,14 +136,19 @@ define([
return clusterPage.goToTab('Networks');
})
.clickByCssSelector('.subtab-link-network_verification')
.assertElementContainsText('.alert-warning', 'At least two online nodes are required',
'Network verification warning appears if only one node added')
.assertElementContainsText(
'.alert-warning',
'At least two online nodes are required',
'Network verification warning appears if only one node added'
)
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.assertElementContainsText('.warnings-block',
.assertElementContainsText(
'.actions-panel .warnings-block',
'Please verify your network settings before deployment',
'Network verification warning is shown')
'Network verification warning is shown'
)
.then(function() {
return dashboardPage.discardChanges();
});
@ -139,18 +156,21 @@ define([
'No controller warning': function() {
return this.remote
.then(function() {
// Adding single compute
return common.addNodesToCluster(1, ['Compute']);
})
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.assertElementDisabled(dashboardPage.deployButtonSelector,
'No deployment should be possible without controller nodes added')
.assertElementDisabled(
dashboardPage.deployButtonSelector,
'No deployment should be possible without controller nodes added'
)
.assertElementExists('div.instruction.invalid', 'Invalid configuration message is shown')
.assertElementContainsText('.environment-alerts ul.text-danger li',
'At least 1 Controller nodes are required (0 selected currently).',
'No controllers added warning should be shown')
.assertElementContainsText(
'.task-alerts ul.text-danger li',
'At least 1 Controller nodes are required (0 selected currently).',
'No controllers added warning should be shown'
)
.then(function() {
return dashboardPage.discardChanges();
});
@ -202,25 +222,48 @@ define([
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.assertElementTextEquals(valueSelector + '.total', total,
'The number of Total nodes in statistics is updated according to added nodes')
.assertElementTextEquals(valueSelector + '.controller', controllerNodes,
'The number of controllerNodes nodes in statistics is updated according to ' +
'added nodes')
.assertElementTextEquals(valueSelector + '.compute', computeNodes,
'The number of Compute nodes in statistics is updated according to added nodes')
.assertElementTextEquals(valueSelector + '.base-os', operatingSystemNodes,
'The number of Operating Systems nodes in statistics is updated according to ' +
'added nodes')
.assertElementTextEquals(valueSelector + '.virt', virtualNodes,
'The number of Virtual nodes in statistics is updated according to added nodes')
.assertElementTextEquals(valueSelector + '.offline', 1,
'The number of Offline nodes in statistics is updated according to added nodes')
.assertElementTextEquals(valueSelector + '.error', 1,
'The number of Error nodes in statistics is updated according to added nodes')
.assertElementTextEquals(valueSelector + '.pending_addition', total,
'The number of Pending Addition nodes in statistics is updated according to ' +
'added nodes')
.assertElementTextEquals(
valueSelector + '.total',
total,
'The number of Total nodes in statistics is correct'
)
.assertElementTextEquals(
valueSelector + '.controller',
controllerNodes,
'The number of controllerNodes nodes in statistics is correct'
)
.assertElementTextEquals(
valueSelector + '.compute',
computeNodes,
'The number of Compute nodes in statistics is correct'
)
.assertElementTextEquals(
valueSelector + '.base-os',
operatingSystemNodes,
'The number of Operating Systems nodes in statistics is correct'
)
.assertElementTextEquals(
valueSelector + '.virt',
virtualNodes,
'The number of Virtual nodes in statistics is corrects'
)
.assertElementTextEquals(
valueSelector + '.offline',
1,
'The number of Offline nodes in statistics is correct'
)
.assertElementTextEquals(
valueSelector + '.error',
1,
'The number of Error nodes in statistics is correct'
)
.assertElementTextEquals(
valueSelector + '.pending_addition',
total,
'The number of Pending Addition nodes in statistics is correct'
)
.clickByCssSelector('.actions-panel .nav button.dropdown-toggle')
.clickByCssSelector('.actions-panel .nav .dropdown-menu li.deploy button')
.then(function() {
return dashboardPage.discardChanges();
});

View File

@ -56,62 +56,105 @@ define([
return clusterPage.goToTab('Dashboard');
});
},
'No deployment button when there are no nodes added': function() {
return this.remote
.assertElementNotExists(dashboardPage.deployButtonSelector,
'No deployment should be possible without nodes added');
},
'Discard changes': function() {
'Provision nodes': function() {
this.timeout = 100000;
return this.remote
.assertElementNotExists(
dashboardPage.deployButtonSelector,
'No deployment should be possible without nodes added'
)
.then(function() {
// Adding three controllers
return common.addNodesToCluster(1, ['Controller']);
})
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.clickByCssSelector('.btn-discard-changes')
.clickByCssSelector('.actions-panel .nav button.dropdown-toggle')
.clickByCssSelector('.actions-panel .nav .dropdown-menu li.provision button')
.assertElementContainsText(
'.actions-panel .changes-list ul li',
'1 node to be provisioned.',
'1 node to be provisioned'
)
.clickByCssSelector('.btn-provision')
.then(function() {
return modal.waitToOpen();
})
.assertElementContainsText('h4.modal-title', 'Discard Changes',
'Discard Changes confirmation modal expected')
.then(function() {
return modal.clickFooterButton('Discard');
return modal.checkTitle('Provision Nodes');
})
.then(function() {
return modal.clickFooterButton('Start Provisioning');
})
.then(function() {
return modal.waitToClose();
})
.assertElementAppears('.dashboard-block a.btn-add-nodes', 2000,
'All changes discarded, add nodes button gets visible in deploy readiness block');
.assertElementAppears('div.deploy-process div.progress', 2000, 'Provisioning started')
.assertElementDisappears('div.deploy-process div.progress', 5000, 'Provisioning finished')
.assertElementContainsText(
'div.alert-success strong',
'Success',
'Provisioning successfully finished'
)
.then(function() {
return clusterPage.isTabLocked('Networks');
})
.then(function(isLocked) {
assert.isFalse(isLocked, 'Networks tab is not locked after nodes were provisioned');
})
.then(function() {
return clusterPage.isTabLocked('Settings');
})
.then(function(isLocked) {
assert.isFalse(isLocked, 'Settings tab is not locked after nodes were provisioned');
})
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.assertElementEnabled(
dashboardPage.deployButtonSelector,
'Provisioned nodes can be deployed'
)
.then(function() {
return clusterPage.resetEnvironment(clusterName);
});
},
'Start/stop deployment': function() {
this.timeout = 100000;
return this.remote
.then(function() {
return common.addNodesToCluster(3, ['Controller']);
return common.addNodesToCluster(2, ['Controller']);
})
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.assertElementAppears('.dashboard-tab', 200, 'Dashboard tab opened')
.then(function() {
return dashboardPage.startDeployment();
})
.assertElementAppears('div.deploy-process div.progress', 2000, 'Deployment started')
.assertElementAppears('button.stop-deployment-btn:not(:disabled)', 5000,
'Stop button appears')
.assertElementAppears(
'button.stop-deployment-btn:not(:disabled)',
5000,
'Stop button appears'
)
.then(function() {
return dashboardPage.stopDeployment();
})
.assertElementDisappears('div.deploy-process div.progress', 20000, 'Deployment stopped')
.assertElementAppears(dashboardPage.deployButtonSelector, 1000,
'Deployment button available')
.assertElementContainsText('div.alert-warning strong', 'Success',
'Deployment successfully stopped alert is expected')
.assertElementNotExists('.go-to-healthcheck',
'Healthcheck link is not visible after stopped deploy')
// Reset environment button is available
.assertElementAppears(
dashboardPage.deployButtonSelector,
3000,
'Deployment button available'
)
.assertElementContainsText(
'div.alert-warning strong',
'Success',
'Deployment successfully stopped alert is expected'
)
.assertElementNotExists(
'.go-to-healthcheck',
'Healthcheck link is not visible after stopped deploy'
)
.then(function() {
return clusterPage.resetEnvironment(clusterName);
});
@ -120,7 +163,6 @@ define([
this.timeout = 100000;
return this.remote
.then(function() {
// Adding single controller (enough for deployment)
return common.addNodesToCluster(1, ['Controller']);
})
.then(function() {
@ -141,8 +183,11 @@ define([
.then(function() {
return dashboardPage.startDeployment();
})
.assertElementDisappears('.dashboard-block .progress', 60000,
'Progress bar disappears after deployment')
.assertElementDisappears(
'.dashboard-block .progress',
60000,
'Progress bar disappears after deployment'
)
.assertElementAppears('.links-block', 5000, 'Deployment completed')
.assertElementExists('.go-to-healthcheck', 'Healthcheck link is visible after deploy')
.findByLinkText('Horizon')
@ -162,8 +207,10 @@ define([
.then(function(isLocked) {
assert.isTrue(isLocked, 'Networks tab should turn locked after deployment');
})
.assertElementEnabled('.add-nodegroup-btn',
'Add Node network group button is enabled after cluster deploy')
.assertElementEnabled(
'.add-nodegroup-btn',
'Add Node network group button is enabled after cluster deploy'
)
.then(function() {
return clusterPage.isTabLocked('Settings');
})

View File

@ -624,6 +624,7 @@
"add_nodes": "Add nodes to the OpenStack Environment.",
"not_deployed_instruction": "Before you proceed, review the environment configuration, including the node disk partitioning and network settings. You will not be able to change these settings after Fuel deploys the OpenStack environment.",
"deployment_cannot_be_started": "Deployment cannot be started due to invalid environment configuration. Please review and address the warnings below before proceeding and see the",
"provisioning_cannot_be_started": "Provisioning of nodes cannot be started due to invalid environment configuration. Please review and address the warnings below before proceeding",
"stop": "Stop",
"healthcheck": "To view the OpenStack health check status go to ",
"healthcheck_tab": "Healthcheck tab",
@ -644,7 +645,46 @@
"horizon_description": "The OpenStack dashboard Horizon is now available.",
"go_to_horizon": "Go to Horizon",
"no_storage_enabled": "No storage option enabled",
"http_plugin_link": "(HTTP)"
"http_plugin_link": "(HTTP)",
"added_node": "Added __count__ node",
"added_node_plural": "Added __count__ nodes",
"deleted_node": "Deleted __count__ node",
"deleted_node_plural": "Deleted __count__ nodes",
"provisioned_node": "Provisioned __count__ node",
"provisioned_node_plural": "Provisioned __count__ nodes",
"verification_not_performed": "Please verify your network settings before deployment.",
"verification_in_progress": "Network verification is in progress.",
"verification_failed": "Networks verification failed with an error.",
"get_more_info": "For more information please visit the",
"networks_link": "Networks tab",
"invalid_settings": "Environment settings are invalid.",
"settings_link": "Settings tab",
"tls_not_enabled": "TLS is not enabled. It is highly recommended to enable and configure TLS.",
"tls_for_horizon_not_enabled": "TLS is not enabled for Horizon public endpoint. It is highly recommended to enable and configure TLS for Horizon.",
"tls_for_services_not_enabled": "TLS is not enabled for OpenStack public endpoints. It is highly recommended to enable and configure TLS.",
"offline_nodes": "Some nodes are offline.",
"unprovisioned_virt_nodes": "The __role__ node is not provisioned.",
"unprovisioned_virt_nodes_plural": "Some __role__ nodes are not provisioned.",
"deployment_mode": "Deployment Mode",
"actions": {
"deploy": {
"title": "Regular deployment",
"button_title": "Deploy Changes"
},
"provision": {
"title": "Advanced provisioning",
"nodes_to_provision": "__count__ node to be provisioned.",
"nodes_to_provision_plural": "__count__ nodes to be provisioned.",
"no_nodes_to_provision": "No online discovered nodes to provision.",
"button_title": "Provision Nodes"
},
"spawn_vms": {
"title": "VMs provisioning",
"nodes_to_provision": "__count__ node to be provisioned.",
"nodes_to_provision_plural": "__count__ nodes to be provisioned.",
"button_title": "Provision VMs"
}
}
},
"network_tab": {
"title": "Network Settings",
@ -798,13 +838,11 @@
"tenant_label": "Tenant",
"tenant_description": "Tenant (project) name for Administrator"
},
"deploy_changes": "Deploy Changes",
"provision_vms": "Provision VMs",
"deploy": "Deploying",
"provision": "Provisioning of nodes",
"reset_environment": "Resetting",
"stop_deployment": "Stopping",
"stop_deployment_button": "Stop deployment",
"deployment_mode": "Deployment Mode",
"show_details_button": "Show additional information",
"hide_details_button": "Hide additional information",
"openstack_release": "OpenStack Release",
@ -944,38 +982,18 @@
"saving_failed_message": "Failed to save changes. Please fix the errors and try again.",
"no_discard_message": "You have unsaved changes on this page. Press Cancel to stay on the page or Save and Proceed to save data and proceed."
},
"display_changes": {
"deploy_cluster": {
"title": "Deploy Changes",
"added_node": "Added __count__ node",
"added_node_plural": "Added __count__ nodes",
"deleted_node": "Deleted __count__ node",
"deleted_node_plural": "Deleted __count__ nodes",
"provisioned_node": "Provisioned __count__ node",
"provisioned_node_plural": "Provisioned __count__ nodes",
"settings_changes": {
"attributes": "Changed OpenStack settings.",
"networks": "Changed network settings.",
"disks": "Changed disk configuration of the following nodes:",
"interfaces": "Changed interface configuration of the following nodes:"
},
"warnings": {
"no_deployment": "Environment configuration is invalid. Cannot start the deployment. Please check the warnings below.",
"connectivity_alert": "Packages and updates are fetched from the repositories defined in the Settings tab. If your environment uses the default repositories, verify that the Fuel Master node can access the Internet. If the Fuel Master node is not connected to the Internet, you must set up a local mirror and configure Fuel to upload packages from the local mirror. Fuel must have network access to the local mirror."
},
"deploy": "Deploy",
"redeployment_needed": "Deployment completed with errors. Please redeploy your environment.",
"verification_not_performed": "Please verify your network settings before deployment.",
"verification_in_progress": "Network verification is in progress.",
"verification_failed": "Networks verification failed with an error.",
"get_more_info": "For more information please visit the",
"networks_link": "Networks tab",
"invalid_settings": "Environment settings are invalid.",
"settings_link": "Settings tab",
"tls_not_enabled": "TLS is not enabled. It is highly recommended to enable and configure TLS.",
"tls_for_horizon_not_enabled": "TLS is not enabled for Horizon public endpoint. It is highly recommended to enable and configure TLS for Horizon.",
"tls_for_services_not_enabled": "TLS is not enabled for OpenStack public endpoints. It is highly recommended to enable and configure TLS.",
"no_deployment": "Environment configuration is invalid. Cannot start the deployment. Please check the warnings below.",
"connectivity_alert": "Packages and updates are fetched from the repositories defined in the Settings tab. If your environment uses the default repositories, verify that the Fuel Master node can access the Internet. If the Fuel Master node is not connected to the Internet, you must set up a local mirror and configure Fuel to upload packages from the local mirror. Fuel must have network access to the local mirror.",
"are_you_sure_deploy": "Click Deploy to start the deployment or Cancel to make changes.",
"offline_nodes": "Some nodes are offline."
"deploy": "Deploy"
},
"provision_nodes": {
"title": "Provision Nodes",
"locked_node_settings_alert": "Before you proceed, review the environment nodes configuration, including disk partitioning and interfaces configuration. You will not be able to change these settings after Fuel provisions the nodes.",
"are_you_sure_provision": "Click Start Provisioning to provision the operating system onto the environment nodes, or Cancel to make changes.",
"start_provisioning": "Start Provisioning"
},
"provision_vms": {
"title": "Provision VMs",
@ -1008,10 +1026,10 @@
"title": "Delete Nodes",
"common_message": "Are you sure you want to delete the selected node from the environment?",
"common_message_plural": "Are you sure you want to delete the selected nodes from the environment?",
"not_deployed_nodes_message": "The not deployed node will return to the pool of unallocated nodes.",
"not_deployed_nodes_message_plural": "Not deployed nodes will return to the pool of unallocated nodes.",
"deployed_nodes_message": "The deployed node will be marked as pending deletion and will be removed from the environment after re-deployment.",
"deployed_nodes_message_plural": "Deployed nodes will be marked as pending deletion and will be removed from the environment after redeployment."
"added_nodes_message": "The node will return to the pool of unallocated nodes.",
"added_nodes_message_plural": "The nodes will return to the pool of unallocated nodes.",
"deployed_nodes_message": "The node will be marked as pending deletion and will be removed from the environment after deployment.",
"deployed_nodes_message_plural": "The nodes will be marked as pending deletion and will be removed from the environment after deployment."
},
"discard_changes": {
"title": "Discard Changes",
@ -1566,7 +1584,13 @@
},
"dashboard_tab": {
"delete_environment": "删除环境",
"alert_delete": "清除每个节点并放回未分配节点池"
"alert_delete": "清除每个节点并放回未分配节点池",
"added_node": "增加 __count__ 个节点。",
"added_node_plural": "增加 __count__ 个节点。",
"deleted_node": "删除 __count__ 个节点。",
"deleted_node_plural": "删除 __count__ 个节点。",
"provisioned_node": "配置了 __count__ 个节点",
"provisioned_node_plural": "配置了 __count__ 个节点"
},
"network_tab": {
"title": "网络设置",
@ -1760,38 +1784,11 @@
"saving_failed_message": "保存失败。请修复错误后再保存。",
"no_discard_message": "你有尚未保存的变更。选择“取消”留在本页,或者“保存并更改”使配置生效。"
},
"display_changes": {
"deploy_cluster": {
"title": "部署变更",
"added_node": "增加 __count__ 个节点。",
"added_node_plural": "增加 __count__ 个节点。",
"deleted_node": "删除 __count__ 个节点。",
"deleted_node_plural": "删除 __count__ 个节点。",
"provisioned_node": "配置了 __count__ 个节点",
"provisioned_node_plural": "配置了 __count__ 个节点",
"settings_changes": {
"attributes": "OpenStack 设置已改变。",
"networks": "网络设置已改变。",
"disks": "以下节点的磁盘配置已改变:",
"interfaces": "以下节点的接口配置已改变:"
},
"warnings": {
"no_deployment": "环境配置有误,无法开始部署。请检查下面的错误。",
"connectivity_alert": "软件包和更新所用的镜像源定义在设置标签页中。如果你的环境使用默认的镜像源,请检查 Fuel 的主节点能访问外网。如果 Fuel 的主节点没有连接外网,你必须搭建一个本地的镜像源,并配置 Fuel 从本地镜像源获取软件包。Fuel 必须能通过网络访问本地镜像源。"
},
"deploy": "部署",
"redeployment_needed": "某些节点配置后状态错误,需要重新部署。",
"verification_not_performed": "请在部署前检测网络。",
"verification_in_progress": "正在进行网络检测。",
"verification_failed": "网络检测遇到问题。",
"get_more_info": "更多信息请访问",
"networks_link": "网络标签页",
"invalid_settings": "环境设置有误。",
"settings_link": "设置标签页",
"tls_not_enabled": "没有启用 TLS。强烈建议启用并设置 TLS。",
"tls_for_horizon_not_enabled": "Horizon 公共接入点没有启用 TLS。强烈建议为 Horizon 启用和配置 TLS。",
"tls_for_services_not_enabled": "OpenStack 公共接入点没有启用 TLS。强烈建议启用和配置 TLS。",
"are_you_sure_deploy": "点击部署开始部署,或者点击取消。",
"offline_nodes": "有些节点离线。"
"no_deployment": "环境配置有误,无法开始部署。请检查下面的错误。",
"connectivity_alert": "软件包和更新所用的镜像源定义在设置标签页中。如果你的环境使用默认的镜像源,请检查 Fuel 的主节点能访问外网。如果 Fuel 的主节点没有连接外网,你必须搭建一个本地的镜像源,并配置 Fuel 从本地镜像源获取软件包。Fuel 必须能通过网络访问本地镜像源。"
},
"provision_vms": {
"title": "配置 VM",
@ -2262,7 +2259,19 @@
"reset_disabled_for_deploying_cluster": "環境のリセットはデプロイ中の環境には使用できません。",
"rename_error": {
"title": "環境の名前変更ができませんでした"
}
},
"added_node": "__count__ードを追加。",
"added_node_plural": "__count__ードを追加。",
"deleted_node": "__count__ードを削除。",
"deleted_node_plural": "__count__ードを削除。",
"locked_settings_alert": "環境設定とノードの設定はデプロイ後に変更できなくなりますので注意してください。",
"verification_not_performed": "デプロイ前にネットワーク設定を確認することをおすすめします。",
"verification_in_progress": "ネットワークの検証が進行中です。",
"verification_failed": "ネットワークの検証がエラーのために失敗しました。",
"get_more_info": "詳しい情報は次を参照してください:",
"networks_link": "ネットワークタブ",
"invalid_settings": "環境設定が不正です。",
"settings_link": "設定タブ"
},
"network_tab": {
"title": "ネットワーク設定",
@ -2489,32 +2498,11 @@
"leave_button": "ページから離れ、変更を破棄",
"default_message": "このページでの変更が保存されていません。変更を破棄してこのページから離れてもよろしいですか?"
},
"display_changes": {
"deploy_cluster": {
"title": "変更をデプロイ",
"added_node": "__count__ードを追加。",
"added_node_plural": "__count__ードを追加。",
"deleted_node": "__count__ードを削除。",
"deleted_node_plural": "__count__ードを削除。",
"settings_changes": {
"attributes": "OpenStack設定を変更。",
"networks": "ネットワーク設定を変更。",
"disks": "以下のノードのディスク構成を変更:",
"interfaces": "以下のノードのインタフェイス構成を変更:"
},
"warnings": {
"no_deployment": "環境の設定が無効です。デプロイは開始できません。次の警告を確認してください。",
"connectivity_alert": "デフォルトではパッケージやパッケージの更新は、リポジトリから取得されます。 Fuelードがインタネットにアクセスできることを確認してください。\n別のリポジトリを指定またはローカルミラーを作成するには、デプロイ前に設定タブを確認してください。"
},
"deploy": "デプロイ",
"redeployment_needed": "いくつかのノードはデプロイ後にステータスがエラーになっています。再デプロイが必要です。",
"locked_settings_alert": "環境設定とノードの設定はデプロイ後に変更できなくなりますので注意してください。",
"verification_not_performed": "デプロイ前にネットワーク設定を確認することをおすすめします。",
"verification_in_progress": "ネットワークの検証が進行中です。",
"verification_failed": "ネットワークの検証がエラーのために失敗しました。",
"get_more_info": "詳しい情報は次を参照してください:",
"networks_link": "ネットワークタブ",
"invalid_settings": "環境設定が不正です。",
"settings_link": "設定タブ"
"no_deployment": "環境の設定が無効です。デプロイは開始できません。次の警告を確認してください。",
"connectivity_alert": "デフォルトではパッケージやパッケージの更新は、リポジトリから取得されます。 Fuelードがインタネットにアクセスできることを確認してください。\n別のリポジトリを指定またはローカルミラーを作成するには、デプロイ前に設定タブを確認してください。"
},
"show_node": {
"manufacturer_label": "製造元",
@ -2813,7 +2801,23 @@
"reset_environment_warning": "이 작업은 기존의 모든 노드를 배치전 상태로 바꾸고 기존의 환경을 삭제합니다.",
"reset_disabled_for_new_cluster": "아직 배치되지 않은 환경에서 환경재설정은 할수없습니다.",
"repeated_reset_disabled": "현재의 환경은 이미 재설정되고 있읍니다.",
"reset_disabled_for_deploying_cluster": "배치되고있는 환경에서 환경재설정은 할수 없습니다.배치 프로세스를 중지하려면 [배치중지]를 사용합니다."
"reset_disabled_for_deploying_cluster": "배치되고있는 환경에서 환경재설정은 할수 없습니다.배치 프로세스를 중지하려면 [배치중지]를 사용합니다.",
"added_node": "__count__ 노드를 추가",
"added_node_plural": "__count__ 노드들을 추가",
"deleted_node": "__count__ 노드를 삭제",
"deleted_node_plural": "__count__ 노드들을 삭제",
"verification_not_performed": "请在部署前检测网络。",
"verification_in_progress": "正在进行网络检测。",
"verification_failed": "网络检测遇到问题。",
"get_more_info": "更多信息请访问",
"networks_link": "网络标签页",
"invalid_settings": "环境设置有误。",
"settings_link": "设置标签页",
"tls_not_enabled": "没有启用 TLS。强烈建议启用并设置 TLS。",
"tls_for_horizon_not_enabled": "Horizon 公共接入点没有启用 TLS。强烈建议为 Horizon 启用和配置 TLS。",
"tls_for_services_not_enabled": "OpenStack 公共接入点没有启用 TLS。强烈建议启用和配置 TLS。",
"are_you_sure_deploy": "点击部署开始部署,或者点击取消。",
"offline_nodes": "有些节点离线。"
},
"network_tab": {
"title": "네트워크 설정",
@ -2985,21 +2989,9 @@
"leave_button": "변경사항을 취소하고 페이지를 떠나가기",
"default_message": "설정이 수정되었지만 저장되지 않았습니다.변경 사항을 취소하고 페이지를 떠나겠습니까?"
},
"display_changes": {
"deploy_cluster": {
"title": "변경내용 배치",
"added_node": "__count__ 노드를 추가",
"added_node_plural": "__count__ 노드들을 추가",
"deleted_node": "__count__ 노드를 삭제",
"deleted_node_plural": "__count__ 노드들을 삭제",
"settings_changes": {
"attributes": "OpenStack 설정",
"networks": "네트워크 설정",
"disks": "다음 노드들의 디스크 설정:"
},
"warnings": {
},
"deploy": "배치되었읍니다",
"redeployment_needed": "일부 노드는 배치후 오류상태가 있습니다. 재배치가 필요합니다."
"deploy": "배치되었읍니다"
},
"show_node": {
"manufacturer_label": "제조업자",

File diff suppressed because it is too large Load Diff

View File

@ -79,9 +79,8 @@ var Node = React.createClass({
e.preventDefault();
if (this.state.actionInProgress) return;
this.setState({actionInProgress: true});
var node = new models.Node(this.props.node.attributes);
var data = {pending_deletion: false};
node.save(data, {patch: true})
new models.Node(this.props.node.attributes)
.save({pending_deletion: false}, {patch: true})
.done(() => {
this.props.cluster.fetchRelated('nodes').done(() => {
this.setState({actionInProgress: false});
@ -302,7 +301,7 @@ var Node = React.createClass({
);
},
renderCompactNode(options) {
var node = this.props.node;
var {node, checked, locked, onNodeSelection, renderActionButtons} = this.props;
var {ns, status, roles, nodePanelClasses, logoClasses, statusClasses, isSelectable} = options;
return (
<div className='compact-node'>
@ -310,12 +309,10 @@ var Node = React.createClass({
<label className='node-box'>
<div
className='node-box-inner clearfix'
onClick={isSelectable &&
_.partial(this.props.onNodeSelection, null, !this.props.checked)
}
onClick={isSelectable && _.partial(onNodeSelection, null, !checked)}
>
<div className='node-checkbox'>
{this.props.checked && <i className='glyphicon glyphicon-ok' />}
{checked && <i className='glyphicon glyphicon-ok' />}
</div>
<div className='node-name'>
<p>{node.get('name') || node.get('mac')}</p>
@ -331,7 +328,7 @@ var Node = React.createClass({
<div className='node-hardware'>
<p>
<span>
{node.resource('cores') || '0'} ({node.resource('ht_cores') || '?'})
{node.resource('cores')} ({node.resource('ht_cores') || '?'})
</span> / <span>
{node.resource('hdd') ? utils.showDiskSize(node.resource('hdd')) : '?' +
i18n('common.size.gb')
@ -364,7 +361,7 @@ var Node = React.createClass({
{this.renderRoleList(roles)}
</div>
}
{!_.isEmpty(this.props.node.get('labels')) &&
{!_.isEmpty(node.get('labels')) &&
<div className='node-labels'>
<i className='glyphicon glyphicon-tags pull-left' />
{this.renderLabels()}
@ -387,20 +384,20 @@ var Node = React.createClass({
{status === 'offline' && this.renderRemoveButton()}
{[
!!node.get('cluster') && this.renderLogsLink(),
this.props.renderActionButtons && node.hasChanges() &&
!this.props.locked &&
renderActionButtons &&
(node.get('pending_addition') || node.get('pending_deletion')) &&
!locked &&
<button
className='btn btn-discard'
key='btn-discard'
onClick={
node.get('pending_deletion') ?
this.discardNodeDeletion : this.showDeleteNodesDialog
onClick={node.get('pending_deletion') ?
this.discardNodeDeletion
:
this.showDeleteNodesDialog
}
>
{i18n(
ns + (node.get('pending_deletion') ?
'discard_deletion' : 'delete_node'
)
{i18n(ns +
(node.get('pending_deletion') ? 'discard_deletion' : 'delete_node')
)}
</button>
]}
@ -425,7 +422,7 @@ var Node = React.createClass({
);
},
renderStandardNode(options) {
var node = this.props.node;
var {node, locked, renderActionButtons} = this.props;
var {ns, status, roles, nodePanelClasses, logoClasses, statusClasses} = options;
return (
<div className={utils.classNames(nodePanelClasses)}>
@ -456,17 +453,21 @@ var Node = React.createClass({
<div className='node-action'>
{[
!!node.get('cluster') && this.renderLogsLink(true),
this.props.renderActionButtons && node.hasChanges() && !this.props.locked &&
renderActionButtons &&
(node.get('pending_addition') || node.get('pending_deletion')) &&
!locked &&
<Tooltip
key={'pending_addition_' + node.id}
text={
i18n(ns + (node.get('pending_deletion') ? 'discard_deletion' : 'delete_node'))
}
key={'discard-node-changes-' + node.id}
text={i18n(ns +
(node.get('pending_deletion') ? 'discard_deletion' : 'delete_node')
)}
>
<div
className='icon btn-discard'
onClick={node.get('pending_deletion') ?
this.discardNodeDeletion : this.showDeleteNodesDialog
this.discardNodeDeletion
:
this.showDeleteNodesDialog
}
/>
</Tooltip>

View File

@ -366,7 +366,7 @@ export var DiscardNodeChangesDialog = React.createClass({
}
});
export var DeployChangesDialog = React.createClass({
export var DeployClusterDialog = React.createClass({
mixins: [
dialogMixin,
// this is needed to somehow handle the case when
@ -379,9 +379,9 @@ export var DeployChangesDialog = React.createClass({
})
],
getDefaultProps() {
return {title: i18n('dialog.display_changes.title')};
return {title: i18n('dialog.deploy_cluster.title')};
},
ns: 'dialog.display_changes.',
ns: 'dialog.deploy_cluster.',
deployCluster() {
this.setState({actionInProgress: true});
dispatcher.trigger('deploymentTasksUpdated');
@ -445,6 +445,72 @@ export var DeployChangesDialog = React.createClass({
}
});
export var ProvisionNodesDialog = React.createClass({
mixins: [dialogMixin],
getDefaultProps() {
return {title: i18n('dialog.provision_nodes.title')};
},
ns: 'dialog.provision_nodes.',
provisionNodes() {
this.setState({actionInProgress: true});
dispatcher.trigger('deploymentTasksUpdated');
var task = new models.Task();
task.save({}, {url: _.result(this.props.cluster, 'url') + '/provision', type: 'PUT'})
.done(() => {
this.close();
dispatcher.trigger('deploymentTaskStarted');
})
.fail(this.showError);
},
renderBody() {
return (
<div className='provision-nodes-dialog'>
<div className='text-warning'>
<i className='glyphicon glyphicon-warning-sign' />
<div className='instruction'>
{i18n(this.ns + 'locked_node_settings_alert') + ' '}
</div>
</div>
<div className='text-warning'>
<i className='glyphicon glyphicon-warning-sign' />
<div className='instruction'>
{i18n('cluster_page.dashboard_tab.package_information') + ' '}
<a
target='_blank'
href={utils.composeDocumentationLink('operations.html#troubleshooting')}
>
{i18n('cluster_page.dashboard_tab.operations_guide')}
</a>
{i18n('cluster_page.dashboard_tab.for_more_information_configuration')}
</div>
</div>
<div className='confirmation-question'>
{i18n(this.ns + 'are_you_sure_provision')}
</div>
</div>
);
},
renderFooter() {
return ([
<button
key='cancel'
className='btn btn-default'
onClick={this.close}
disabled={this.state.actionInProgress}
>
{i18n('common.cancel_button')}
</button>,
<button key='provisioning'
className='btn start-provision-btn btn-success'
disabled={this.state.actionInProgress}
onClick={this.provisionNodes}
>
{i18n(this.ns + 'start_provisioning')}
</button>
]);
}
});
export var ProvisionVMsDialog = React.createClass({
mixins: [dialogMixin],
getDefaultProps() {
@ -1253,17 +1319,20 @@ export var DeleteNodesDialog = React.createClass({
},
renderBody() {
var ns = 'dialog.delete_nodes.';
var notDeployedNodesAmount = this.props.nodes.reject({status: 'ready'}).length;
var deployedNodesAmount = this.props.nodes.length - notDeployedNodesAmount;
var {nodes} = this.props;
var addedNodes = nodes.where({pending_addition: true});
return (
<div className='text-danger'>
{this.renderImportantLabel()}
{i18n(ns + 'common_message', {count: this.props.nodes.length})}
<br/>
{!!notDeployedNodesAmount && i18n(ns + 'not_deployed_nodes_message',
{count: notDeployedNodesAmount})}
{!!addedNodes.length &&
i18n(ns + 'added_nodes_message', {count: addedNodes.length})
}
{' '}
{!!deployedNodesAmount && i18n(ns + 'deployed_nodes_message', {count: deployedNodesAmount})}
{!!(nodes.length - addedNodes.length) &&
i18n(ns + 'deployed_nodes_message', {count: nodes.length - addedNodes.length})
}
</div>
);
},
@ -1286,19 +1355,17 @@ export var DeleteNodesDialog = React.createClass({
deleteNodes() {
this.setState({actionInProgress: true});
var nodes = new models.Nodes(this.props.nodes.map((node) => {
// mark deployed node as pending deletion
if (node.get('status') === 'ready') {
if (node.get('pending_addition')) {
return {
id: node.id,
pending_deletion: true
cluster_id: null,
pending_addition: false,
pending_roles: []
};
}
// remove not deployed node from cluster
return {
id: node.id,
cluster_id: null,
pending_addition: false,
pending_roles: []
pending_deletion: true
};
}));
Backbone.sync('update', nodes)