From 1bb0136c8e9575e7113cd38d44effbc02a8127e3 Mon Sep 17 00:00:00 2001 From: Brian Federle Date: Tue, 16 Apr 2013 12:41:04 -0700 Subject: [PATCH 01/47] Add granular settings widget --- ui/index.jsp | 1 + ui/scripts/ui-custom/granularSettings.js | 46 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 ui/scripts/ui-custom/granularSettings.js diff --git a/ui/index.jsp b/ui/index.jsp index 46f49f00984..ce1183d4924 100644 --- a/ui/index.jsp +++ b/ui/index.jsp @@ -1647,6 +1647,7 @@ under the License. + diff --git a/ui/scripts/ui-custom/granularSettings.js b/ui/scripts/ui-custom/granularSettings.js new file mode 100644 index 00000000000..02d5c1fcbaa --- /dev/null +++ b/ui/scripts/ui-custom/granularSettings.js @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. + +(function($, cloudStack) { + cloudStack.uiCustom.granularSettings = function(args) { + var dataProvider = args.dataProvider; + var actions = args.actions; + + return function(args) { + var context = args.context; + + var listView = { + id: 'settings', + fields: { + name: { label: 'label.name' }, + value: { label: 'label.value', editable: true } + }, + actions: { + edit: { + label: 'label.change.value', + action: actions.edit + } + }, + dataProvider: dataProvider + }; + + var $listView = $('
').listView({ context: context, listView: listView }); + + return $listView; + } + }; +}(jQuery, cloudStack)); From 019e8cd0ff8fe45d0e867a42e9643c8b571c1725 Mon Sep 17 00:00:00 2001 From: Brian Federle Date: Tue, 16 Apr 2013 12:41:20 -0700 Subject: [PATCH 02/47] Add zone-level granular settings UI --- ui/scripts/system.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index a9054125774..11ae1f20e47 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -5381,6 +5381,28 @@ } } } + }, + + // Granular settings for zone + settings: { + title: 'label.menu.global.settings', + custom: cloudStack.uiCustom.granularSettings({ + dataProvider: function(args) { + args.response.success({ + data: [ + { name: 'config.param.1', value: 1 }, + { name: 'config.param.2', value: 2 } + ] + }); + }, + actions: { + edit: function(args) { + debugger; + // call updateZoneLevelParamter + args.response.success(); + } + } + }) } } } From 59d0541bac54b805dc80ef575dea097d8314a849 Mon Sep 17 00:00:00 2001 From: Brian Federle Date: Tue, 16 Apr 2013 12:46:23 -0700 Subject: [PATCH 03/47] Add cluster-level parameters --- ui/scripts/system.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 11ae1f20e47..2a18eed1f42 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -5397,7 +5397,6 @@ }, actions: { edit: function(args) { - debugger; // call updateZoneLevelParamter args.response.success(); } @@ -8800,6 +8799,27 @@ }); } } + }, + + // Granular settings for cluster + settings: { + title: 'label.menu.global.settings', + custom: cloudStack.uiCustom.granularSettings({ + dataProvider: function(args) { + args.response.success({ + data: [ + { name: 'config.param.1', value: 1 }, + { name: 'config.param.2', value: 2 } + ] + }); + }, + actions: { + edit: function(args) { + // call updateClusterLevelParameters + args.response.success(); + } + } + }) } } } From e8295dd4b0fe40134824a1c971aae63579505e44 Mon Sep 17 00:00:00 2001 From: Brian Federle Date: Tue, 16 Apr 2013 12:46:44 -0700 Subject: [PATCH 04/47] Add primary storage-level parameters UI --- ui/scripts/system.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 2a18eed1f42..46dd6045840 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -10439,6 +10439,27 @@ } }); } + }, + + // Granular settings for storage pool + settings: { + title: 'label.menu.global.settings', + custom: cloudStack.uiCustom.granularSettings({ + dataProvider: function(args) { + args.response.success({ + data: [ + { name: 'config.param.1', value: 1 }, + { name: 'config.param.2', value: 2 } + ] + }); + }, + actions: { + edit: function(args) { + // call updateStorageLevelParameters + args.response.success(); + } + } + }) } } } From 9130cd04995a0262bde5347c5a7f750fa33615ba Mon Sep 17 00:00:00 2001 From: Brian Federle Date: Tue, 16 Apr 2013 12:46:58 -0700 Subject: [PATCH 05/47] Add seondary storage-level parameters UI --- ui/scripts/system.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 46dd6045840..23822b26972 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -10653,6 +10653,27 @@ } }); } + }, + + // Granular settings for storage pool + settings: { + title: 'label.menu.global.settings', + custom: cloudStack.uiCustom.granularSettings({ + dataProvider: function(args) { + args.response.success({ + data: [ + { name: 'config.param.1', value: 1 }, + { name: 'config.param.2', value: 2 } + ] + }); + }, + actions: { + edit: function(args) { + // call updateStorageLevelParameters + args.response.success(); + } + } + }) } } } From ad38060e3a9fd45dda7de4fd10450b116db2283a Mon Sep 17 00:00:00 2001 From: Brian Federle Date: Tue, 16 Apr 2013 12:47:08 -0700 Subject: [PATCH 06/47] Add account-level parameter UI --- ui/scripts/accounts.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ui/scripts/accounts.js b/ui/scripts/accounts.js index 8353d70536e..e663db856bf 100644 --- a/ui/scripts/accounts.js +++ b/ui/scripts/accounts.js @@ -895,6 +895,27 @@ } }); } + }, + + // Granular settings for account + settings: { + title: 'label.menu.global.settings', + custom: cloudStack.uiCustom.granularSettings({ + dataProvider: function(args) { + args.response.success({ + data: [ + { name: 'config.param.1', value: 1 }, + { name: 'config.param.2', value: 2 } + ] + }); + }, + actions: { + edit: function(args) { + // call updateAccountLevelParameters + args.response.success(); + } + } + }) } } } From b54f643b91a144e5f67dd21e8d49e8ee8f7a69a5 Mon Sep 17 00:00:00 2001 From: Brian Federle Date: Tue, 16 Apr 2013 12:51:07 -0700 Subject: [PATCH 07/47] Always maximize detail views with settings tab (to fit listView layout) --- ui/scripts/accounts.js | 1 + ui/scripts/system.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ui/scripts/accounts.js b/ui/scripts/accounts.js index e663db856bf..cea748d2305 100644 --- a/ui/scripts/accounts.js +++ b/ui/scripts/accounts.js @@ -277,6 +277,7 @@ detailView: { name: 'Account details', + isMaximized: true, viewAll: { path: 'accounts.users', label: 'label.users' }, actions: { diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 23822b26972..d44a6944ad7 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -10255,6 +10255,7 @@ detailView: { name: "Primary storage details", + isMaximized: true, actions: { edit: { label: 'label.edit', @@ -10598,6 +10599,7 @@ detailView: { name: 'Secondary storage details', + isMaximized: true, actions: { remove: { label: 'label.action.delete.secondary.storage' , From 71a17e4c139a5c4e4be326e15548e0d6036dfd9a Mon Sep 17 00:00:00 2001 From: Marcus Sorensen Date: Wed, 1 May 2013 14:10:56 -0600 Subject: [PATCH 08/47] CLOUDSTACK-2110 : allow vm to have multiple dhcp entries on same router Signed-off-by: Marcus Sorensen 1367439056 -0600 --- patches/systemvm/debian/config/root/edithosts.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/patches/systemvm/debian/config/root/edithosts.sh b/patches/systemvm/debian/config/root/edithosts.sh index 1f98fbf96ae..8609da79efd 100755 --- a/patches/systemvm/debian/config/root/edithosts.sh +++ b/patches/systemvm/debian/config/root/edithosts.sh @@ -113,7 +113,8 @@ if [ $ipv6 ] then sed -i /$ipv6,/d $DHCP_HOSTS fi -sed -i /$host,/d $DHCP_HOSTS +# don't want to do this in the future, we can have same VM with multiple nics/entries +#sed -i /$host,/d $DHCP_HOSTS #put in the new entry From 38071e63d252fda79d1233196166e4e6c88b08bc Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Thu, 2 May 2013 02:33:27 +0530 Subject: [PATCH 09/47] CLOUDSTACK-2113:Router VM scale up UI support --- ui/scripts/system.js | 81 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 7aa0566fbef..a8c08051149 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -6278,6 +6278,80 @@ } }, + scaleUp:{ + label:'scaleUp Router VM', + createForm: { + title: 'label.change.service.offering', + desc: '', + fields: { + + serviceOfferingId: { + label: 'label.compute.offering', + select: function(args) { + $.ajax({ + url: createURL('listServiceOfferings'), + data: { + issystem: true, + systemvmtype: 'domainrouter' + }, + success: function(json) { + var serviceofferings = json.listserviceofferingsresponse.serviceoffering; + var items = []; + $(serviceofferings).each(function() { + // if(this.id != args.context.routers[0].serviceofferingid) { + items.push({id: this.id, description: this.name}); //default one (i.e. "System Offering For Software Router") doesn't have displaytext property. So, got to use name property instead. + + }); + args.response.success({data: items}); + } + }); + } + } + } + }, + + action: function(args) { + $.ajax({ + url: createURL("scaleVirtualMachine&id=" + args.context.routers[0].id + "&serviceofferingid=" + args.data.serviceOfferingId), + dataType: "json", + async: true, + success: function(json) { + var jid = json.scalevirtualmachineresponse.jobid; + args.response.success( + {_custom: + {jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.virtualmachine; + }, + getActionFilter: function() { + return vmActionfilter; + } + } + } + ); + }, + error:function(json){ + args.response.error(parseXMLHttpResponse(json)); + } + + }); + }, + messages: { + confirm: function(args) { + return 'Do you really want to scale up the Router VM ?'; + }, + notification: function(args) { + + return 'Router VM Scaled Up'; + } + }, + notification: { + poll: pollAsyncJobResult + } + + }, + + viewConsole: { label: 'label.view.console', action: { @@ -11816,9 +11890,9 @@ if (jsonObj.state == 'Running') { allowedActions.push("stop"); - + allowedActions.push("scaleUp"); // if(jsonObj.vpcid != null) - allowedActions.push("restart"); + allowedActions.push("restart"); allowedActions.push("viewConsole"); if (isAdmin()) @@ -11826,7 +11900,8 @@ } else if (jsonObj.state == 'Stopped') { allowedActions.push("start"); - allowedActions.push("remove"); + allowedActions.push("scaleUp"); + allowedActions.push("remove"); if(jsonObj.vpcid != null) allowedActions.push("changeService"); From 996d2f5a2ba9f9af1c9976dd1598655bc258b720 Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Wed, 1 May 2013 13:57:20 -0700 Subject: [PATCH 10/47] CLOUDSTACK-2074: UI - affinity groups - VM wizard under Affinity Groups menu - step 5(select affinity group step) - make corresponding affinity group checked by default. --- ui/scripts/instanceWizard.js | 10 ++++++++- ui/scripts/ui-custom/instanceWizard.js | 30 +++++++++++++++++--------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/ui/scripts/instanceWizard.js b/ui/scripts/instanceWizard.js index f3deb66d6ba..ff130d3e301 100644 --- a/ui/scripts/instanceWizard.js +++ b/ui/scripts/instanceWizard.js @@ -317,7 +317,15 @@ url: createURL('listAffinityGroups'), success: function(json) { var items = json.listaffinitygroupsresponse.affinitygroup; - args.response.success({data: {affinityGroups: items}}); + var data = { + affinityGroups: items + }; + if('affinityGroups' in args.context) { + $.extend(data, { + selectedObj: args.context.affinityGroups[0] + }); + } + args.response.success({data: data}); } }); }, diff --git a/ui/scripts/ui-custom/instanceWizard.js b/ui/scripts/ui-custom/instanceWizard.js index bdc05c11bf7..ea7acf8dca5 100644 --- a/ui/scripts/ui-custom/instanceWizard.js +++ b/ui/scripts/ui-custom/instanceWizard.js @@ -86,7 +86,7 @@ }); }; - var makeSelects = function(name, data, fields, options) { + var makeSelects = function(name, data, fields, options, selectedObj) { var $selects = $('
'); options = options ? options : {}; @@ -151,7 +151,11 @@ .append($('
').addClass('desc').html(_s(this[fields.desc]))) ) .data('json-obj', this); - + + if(selectedObj != null && selectedObj.id == item.id) { + $select.find('input[type=checkbox]').attr('checked', 'checked'); + } + $selects.append($select); if (item._singleSelect) { @@ -493,14 +497,20 @@ ) ); $step.find('.select-container').append( - makeSelects('affinity-groups', args.data.affinityGroups, { - name: 'name', - desc: 'description', - id: 'id' - }, { - type: 'checkbox', - 'wizard-field': 'affinity-groups' - }) + makeSelects( + 'affinity-groups', + args.data.affinityGroups, + { + name: 'name', + desc: 'description', + id: 'id' + }, + { + type: 'checkbox', + 'wizard-field': 'affinity-groups' + }, + args.data.selectedObj + ) ); } else { $step.find('.select-container').append($('

').html(_l('message.no.affinity.groups'))); From 60a8e881c1cbb6f7cd855a9fb13f2f78c91a3fd1 Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Wed, 1 May 2013 14:22:35 -0700 Subject: [PATCH 11/47] CLOUDSTACK-2120: mixed zone management - UI - system.js - remove obsolete code that has been replaced with new change in global function createURL(). --- ui/scripts/system.js | 129 ++++++++++--------------------------------- 1 file changed, 29 insertions(+), 100 deletions(-) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index a8c08051149..e3852a6b040 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -184,16 +184,9 @@ dashboard: { dataProvider: function(args) { var dataFns = { - zoneCount: function(data) { - var data = {}; - if(cloudStack.context.zoneType != null && cloudStack.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data, { - networktype: cloudStack.context.zoneType - }); - } + zoneCount: function(data) { $.ajax({ - url: createURL('listZones'), - data: data, + url: createURL('listZones'), success: function(json) { dataFns.podCount($.extend(data, { zoneCount: json.listzonesresponse.count ? @@ -250,12 +243,7 @@ type: 'routing', page: 1, pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. - }; - if(cloudStack.context.zoneType != null && cloudStack.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data2, { - zonetype: cloudStack.context.zoneType - }); - } + }; $.ajax({ url: createURL('listHosts'), data: data2, @@ -272,12 +260,7 @@ var data2 = { page: 1, pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. - }; - if(cloudStack.context.zoneType != null && cloudStack.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data2, { - zonetype: cloudStack.context.zoneType - }); - } + }; $.ajax({ url: createURL('listStoragePools'), data: data2, @@ -294,12 +277,7 @@ type: 'SecondaryStorage', page: 1, pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. - }; - if(cloudStack.context.zoneType != null && cloudStack.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data2, { - zonetype: cloudStack.context.zoneType - }); - } + }; $.ajax({ url: createURL('listHosts'), data: data2, @@ -333,12 +311,7 @@ projectid: -1, page: 1, pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. - }; - if(cloudStack.context.zoneType != null && cloudStack.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data2, { - zonetype: cloudStack.context.zoneType - }); - } + }; $.ajax({ url: createURL('listRouters'), data: data2, @@ -349,12 +322,7 @@ listAll: true, page: 1, pagesize: 1 //specifying pagesize as 1 because we don't need any embedded objects to be returned here. The only thing we need from API response is "count" property. - }; - if(cloudStack.context.zoneType != null && cloudStack.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data3, { - zonetype: cloudStack.context.zoneType - }); - } + }; $.ajax({ url: createURL('listRouters'), data: data3, @@ -2332,12 +2300,7 @@ var data2 = { forvpc: false - }; - if(cloudStack.context.zoneType != null && cloudStack.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data2, { - zonetype: cloudStack.context.zoneType - }); - } + }; var routers = []; $.ajax({ url: createURL("listRouters&zoneid=" + selectedZoneObj.id + "&listAll=true&page=" + args.page + "&pagesize=" + pageSize + array1.join("")), @@ -2817,12 +2780,7 @@ var data2 = { forvpc: true - }; - if(cloudStack.context.zoneType != null && cloudStack.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data2, { - zonetype: cloudStack.context.zoneType - }); - } + }; var routers = []; $.ajax({ url: createURL("listRouters&zoneid=" + selectedZoneObj.id + "&listAll=true&page=" + args.page + "&pagesize=" + pageSize + array1.join("")), @@ -4708,12 +4666,7 @@ break; } } - } - - if(args.context.zoneType != null && args.context.zoneType.length > 0) { //Basic type or Advanced type - array1.push("&networktype=" + args.context.zoneType); - } - + } $.ajax({ url: createURL("listZones&page=" + args.page + "&pagesize=" + pageSize + array1.join("")), dataType: "json", @@ -5660,12 +5613,12 @@ var searchByArgs = args.filterBy.search.value.length ? '&name=' + args.filterBy.search.value : ''; - var data = { page: args.page, pageSize: pageSize, type: 'routing', listAll: true }; - if(args.context.zoneType != null && args.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data, { - zonetype: args.context.zoneType - }); - } + var data = { + page: args.page, + pageSize: pageSize, + type: 'routing', + listAll: true + }; $.ajax({ url: createURL('listHosts' + searchByArgs), @@ -5714,11 +5667,7 @@ pageSize: pageSize, listAll: true }; - if(args.context.zoneType != null && args.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data, { - zonetype: args.context.zoneType - }); - } + $.ajax({ url: createURL('listStoragePools' + searchByArgs), data: data, @@ -5761,12 +5710,12 @@ var searchByArgs = args.filterBy.search.value.length ? '&name=' + args.filterBy.search.value : ''; - var data = { type: 'SecondaryStorage', page: args.page, pageSize: pageSize, listAll: true }; - if(args.context.zoneType != null && args.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data, { - zonetype: args.context.zoneType - }); - } + var data = { + type: 'SecondaryStorage', + page: args.page, + pageSize: pageSize, + listAll: true + }; $.ajax({ url: createURL('listHosts' + searchByArgs), @@ -5850,18 +5799,11 @@ var listView = $.extend(true, {}, cloudStack.sections.system.subsections.virtualRouters.listView, { dataProvider: function (args) { var searchByArgs = args.filterBy.search.value.length ? - '&name=' + args.filterBy.search.value : ''; - - var data2 = {}; - if(cloudStack.context.zoneType != null && cloudStack.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data2, { - zonetype: cloudStack.context.zoneType - }); - } + '&name=' + args.filterBy.search.value : ''; + var routers = []; $.ajax({ - url: createURL("listRouters&listAll=true&page=" + args.page + "&pagesize=" + pageSize + searchByArgs), - data: data2, + url: createURL("listRouters&listAll=true&page=" + args.page + "&pagesize=" + pageSize + searchByArgs), async: true, success: function(json) { var items = json.listroutersresponse.router ? @@ -5873,8 +5815,7 @@ // Get project routers $.ajax({ - url: createURL("listRouters&listAll=true&page=" + args.page + "&pagesize=" + pageSize + "&projectid=-1"), - data: data2, + url: createURL("listRouters&listAll=true&page=" + args.page + "&pagesize=" + pageSize + "&projectid=-1"), async: true, success: function(json) { var items = json.listroutersresponse.router ? @@ -5961,11 +5902,7 @@ var data2 = { forvpc: false }; - if(cloudStack.context.zoneType != null && cloudStack.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data2, { - zonetype: cloudStack.context.zoneType - }); - } + var routers = []; $.ajax({ url: createURL("listRouters&zoneid=" + selectedZoneObj.id + "&listAll=true&page=" + args.page + "&pagesize=" + pageSize + array1.join("")), @@ -9209,11 +9146,7 @@ } else { array1.push("&hostid=" + args.context.instances[0].hostid); } - - if(args.context.zoneType != null && args.context.zoneType.length > 0) { //Basic type or Advanced type - array1.push("&zonetype=" + args.context.zoneType); - } - + $.ajax({ url: createURL("listHosts&type=Routing" + array1.join("") + "&page=" + args.page + "&pagesize=" + pageSize), dataType: "json", @@ -10821,10 +10754,6 @@ } array1.push("&zoneid=" + args.context.zones[0].id); - if(args.context.zoneType != null && args.context.zoneType.length > 0) { //Basic type or Advanced type - array1.push("&zonetype=" + args.context.zoneType); - } - $.ajax({ url: createURL("listHosts&type=SecondaryStorage&page=" + args.page + "&pagesize=" + pageSize + array1.join("")), dataType: "json", From 58648c4b5354107ca43b768875938d352ac72558 Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Thu, 2 May 2013 02:58:27 +0530 Subject: [PATCH 12/47] CLOUDSTACK-2175: Add private Gateway can aonly be done by root or domain admin but not by normal user --- ui/scripts/vpc.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/scripts/vpc.js b/ui/scripts/vpc.js index a4834f26c72..db964e6ffcd 100644 --- a/ui/scripts/vpc.js +++ b/ui/scripts/vpc.js @@ -737,6 +737,12 @@ actions:{ add:{ label:'Add Private Gateway', + preFilter: function(args) { + if(isAdmin() || isDomainAdmin() ) + return true; + else + return false; + }, createForm:{ title: 'label.add.new.gateway', desc: 'message.add.new.gateway.to.vpc', From 57167d2ca6578e36843fc7fe535fec336170fdf9 Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Wed, 1 May 2013 14:38:10 -0700 Subject: [PATCH 13/47] CLOUDSTACK-2120: mixed zone management - UI - templates.js - remove obsolete code that has been replaced with new change in global function createURL(). --- ui/scripts/templates.js | 106 ++++++++++------------------------------ 1 file changed, 26 insertions(+), 80 deletions(-) diff --git a/ui/scripts/templates.js b/ui/scripts/templates.js index 52e1135c681..91038b904fa 100644 --- a/ui/scripts/templates.js +++ b/ui/scripts/templates.js @@ -116,27 +116,13 @@ dataType: "json", async: true, success: function(json) { - var zoneObjs; - if(args.context.zoneType == null || args.context.zoneType == '') { //all types - zoneObjs = []; - var items = json.listzonesresponse.zone; - if(items != null) { - for(var i = 0; i < items.length; i++) { - zoneObjs.push({id: items[i].id, description: items[i].name}); - } + var zoneObjs= []; + var items = json.listzonesresponse.zone; + if(items != null) { + for(var i = 0; i < items.length; i++) { + zoneObjs.push({id: items[i].id, description: items[i].name}); } - } - else { //Basic type or Advanced type - zoneObjs = []; - var items = json.listzonesresponse.zone; - if(items != null) { - for(var i = 0; i < items.length; i++) { - if(items[i].networktype == args.context.zoneType) { - zoneObjs.push({id: items[i].id, description: items[i].name}); - } - } - } - } + } if (isAdmin() && !(cloudStack.context.projects && cloudStack.context.projects[0])){ zoneObjs.unshift({id: -1, description: "All Zones"}); } @@ -548,27 +534,14 @@ async: true, success: function(json) { var zoneObjs = []; - var items = json.listzonesresponse.zone; - if(args.context.zoneType == null || args.context.zoneType == '') { //all types - if(items != null) { - for(var i = 0; i < items.length; i++) { - if(items[i].id != args.context.templates[0].zoneid) { //destination zone must be different from source zone - zoneObjs.push({id: items[i].id, description: items[i].name}); - } - } + var items = json.listzonesresponse.zone; + if(items != null) { + for(var i = 0; i < items.length; i++) { + if(items[i].id != args.context.templates[0].zoneid) { //destination zone must be different from source zone + zoneObjs.push({id: items[i].id, description: items[i].name}); + } } - } - else { //Basic type or Advanced type - if(items != null) { - for(var i = 0; i < items.length; i++) { - if(items[i].networktype == args.context.zoneType) { //type must be matched - if(items[i].id != args.context.templates[0].zoneid) { //destination zone must be different from source zone - zoneObjs.push({id: items[i].id, description: items[i].name}); - } - } - } - } - } + } args.response.success({data: zoneObjs}); } }); @@ -894,27 +867,13 @@ dataType: "json", async: true, success: function(json) { - var zoneObjs; - if(args.context.zoneType == null || args.context.zoneType == '') { //all types - zoneObjs = []; - var items = json.listzonesresponse.zone; - if(items != null) { - for(var i = 0; i < items.length; i++) { - zoneObjs.push({id: items[i].id, description: items[i].name}); - } + var zoneObjs = []; + var items = json.listzonesresponse.zone; + if(items != null) { + for(var i = 0; i < items.length; i++) { + zoneObjs.push({id: items[i].id, description: items[i].name}); } - } - else { //Basic type or Advanced type - zoneObjs = []; - var items = json.listzonesresponse.zone; - if(items != null) { - for(var i = 0; i < items.length; i++) { - if(items[i].networktype == args.context.zoneType) { - zoneObjs.push({id: items[i].id, description: items[i].name}); - } - } - } - } + } if (isAdmin() && !(cloudStack.context.projects && cloudStack.context.projects[0])){ zoneObjs.unshift({id: -1, description: "All Zones"}); } @@ -1224,27 +1183,14 @@ async: true, success: function(json) { var zoneObjs = []; - var items = json.listzonesresponse.zone; - if(args.context.zoneType == null || args.context.zoneType == '') { //all types - if(items != null) { - for(var i = 0; i < items.length; i++) { - if(items[i].id != args.context.isos[0].zoneid) { //destination zone must be different from source zone - zoneObjs.push({id: items[i].id, description: items[i].name}); - } - } + var items = json.listzonesresponse.zone; + if(items != null) { + for(var i = 0; i < items.length; i++) { + if(items[i].id != args.context.isos[0].zoneid) { //destination zone must be different from source zone + zoneObjs.push({id: items[i].id, description: items[i].name}); + } } - } - else { //Basic type or Advanced type - if(items != null) { - for(var i = 0; i < items.length; i++) { - if(items[i].networktype == args.context.zoneType) { //type must be matched - if(items[i].id != args.context.isos[0].zoneid) { //destination zone must be different from source zone - zoneObjs.push({id: items[i].id, description: items[i].name}); - } - } - } - } - } + } args.response.success({data: zoneObjs}); } }); From 4c7e0b308a9c2718e5f1eb1bff9a6c9b1f49affe Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Wed, 1 May 2013 14:41:39 -0700 Subject: [PATCH 14/47] CLOUDSTACK-2120: mixed zone management - UI - storage.js - remove obsolete code that has been replaced with new change in global function createURL(). --- ui/scripts/storage.js | 44 ++++++------------------------------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/ui/scripts/storage.js b/ui/scripts/storage.js index 9b66083d4e6..d73974a89f5 100644 --- a/ui/scripts/storage.js +++ b/ui/scripts/storage.js @@ -89,20 +89,7 @@ dataType: "json", async: true, success: function(json) { - var zoneObjs; - if(args.context.zoneType == null || args.context.zoneType == '') { //all types - zoneObjs = json.listzonesresponse.zone; - } - else { //Basic type or Advanced type - zoneObjs = []; - var items = json.listzonesresponse.zone; - if(items != null) { - for(var i = 0; i < items.length; i++) { - if(items[i].networktype == args.context.zoneType) - zoneObjs.push(items[i]); - } - } - } + var zoneObjs = json.listzonesresponse.zone; args.response.success({descriptionField: 'name', data: zoneObjs}); } }); @@ -227,20 +214,7 @@ dataType: "json", async: true, success: function(json) { - var zoneObjs; - if(args.context.zoneType == null || args.context.zoneType == '') { //all types - zoneObjs = json.listzonesresponse.zone; - } - else { //Basic type or Advanced type - zoneObjs = []; - var items = json.listzonesresponse.zone; - if(items != null) { - for(var i = 0; i < items.length; i++) { - if(items[i].networktype == args.context.zoneType) - zoneObjs.push(items[i]); - } - } - } + var zoneObjs = json.listzonesresponse.zone; args.response.success({descriptionField: 'name', data: zoneObjs}); } }); @@ -398,18 +372,12 @@ if(args.context != null) { if("instances" in args.context) { - $.extend(data, { - virtualMachineId: args.context.instances[0].id - }); + $.extend(data, { + virtualMachineId: args.context.instances[0].id + }); } } - - if(args.context.zoneType != null && args.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data, { - zonetype: args.context.zoneType - }); - } - + $.ajax({ url: createURL('listVolumes'), data: data, From 50a3c918c894ccdfbdb2a65fc5a8587c67061e01 Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Wed, 1 May 2013 14:44:58 -0700 Subject: [PATCH 15/47] CLOUDSTACK-2120: mixed zone management - UI - instances.js - remove obsolete code that has been replaced with new change in global function createURL(). --- ui/scripts/instances.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 751c3605276..ab9d606b2b6 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -204,13 +204,7 @@ affinitygroupid: args.context.affinityGroups[0].id }); } - - if(args.context.zoneType != null && args.context.zoneType.length > 0) { //Basic type or Advanced type - $.extend(data, { - zonetype: args.context.zoneType - }); - } - + $.ajax({ url: createURL('listVirtualMachines'), data: data, From 3d10f700f97d0daee7e6d69e4436c7955bc7e13a Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Wed, 1 May 2013 16:13:20 -0700 Subject: [PATCH 16/47] CLOUDSTACK-2120: API - mixed zone management - fix a bug that listTemplates/listIsos API didn't take in zoneType properly. --- .../cloud/storage/dao/VMTemplateDaoImpl.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/server/src/com/cloud/storage/dao/VMTemplateDaoImpl.java b/server/src/com/cloud/storage/dao/VMTemplateDaoImpl.java index 71010719333..25ae933740d 100755 --- a/server/src/com/cloud/storage/dao/VMTemplateDaoImpl.java +++ b/server/src/com/cloud/storage/dao/VMTemplateDaoImpl.java @@ -562,10 +562,15 @@ public class VMTemplateDaoImpl extends GenericDaoBase implem sql = SELECT_TEMPLATE_HOST_REF; groupByClause = " GROUP BY t.id, h.data_center_id "; } - if (((templateFilter == TemplateFilter.featured) || (templateFilter == TemplateFilter.community)) ||(zoneType != null && zoneId != null)) { + if ((templateFilter == TemplateFilter.featured) || (templateFilter == TemplateFilter.community)) { dataCenterJoin = " INNER JOIN data_center dc on (h.data_center_id = dc.id)"; } - + + if (zoneType != null) { + dataCenterJoin = " INNER JOIN template_host_ref thr on (t.id = thr.template_id) INNER JOIN host h on (thr.host_id = h.id)"; + dataCenterJoin += " INNER JOIN data_center dc on (h.data_center_id = dc.id)"; + } + if (templateFilter == TemplateFilter.sharedexecutable || templateFilter == TemplateFilter.shared ){ lpjoin = " INNER JOIN launch_permission lp ON t.id = lp.template_id "; } @@ -783,13 +788,15 @@ public class VMTemplateDaoImpl extends GenericDaoBase implem sql += " AND h.data_center_id = " +zoneId; } }else if (zoneId != null){ - sql += " AND tzr.zone_id = " +zoneId+ " AND tzr.removed is null" ; - if (zoneType != null){ - sql += " AND dc.networktype = " + zoneType; - } + sql += " AND tzr.zone_id = " +zoneId+ " AND tzr.removed is null" ; }else{ sql += " AND tzr.removed is null "; } + + if (zoneType != null){ + sql += " AND dc.networktype = '" + zoneType + "'"; + } + if (!showDomr){ sql += " AND t.type != '" +Storage.TemplateType.SYSTEM.toString() + "'"; } From 48540dcf5621d0cd7a925e8da299a3ef2d7d0c31 Mon Sep 17 00:00:00 2001 From: Parth Jagirdar Date: Wed, 1 May 2013 10:44:26 -0700 Subject: [PATCH 17/47] CLOUDSTACK-2282: Selenium Headless configuration using PhantomJS. Description: Putting in selenium_headless support. Command Line parameter added for management server ip. --- test/selenium/ReadMe.txt | 36 ++++++++++++++++------- test/selenium/lib/initialize.py | 17 ++++++++++- test/selenium/smoke/Login_and_Accounts.py | 5 ++-- test/selenium/smoke/main.py | 2 +- 4 files changed, 45 insertions(+), 15 deletions(-) diff --git a/test/selenium/ReadMe.txt b/test/selenium/ReadMe.txt index 30b0e0df7a0..bc968f1dc93 100644 --- a/test/selenium/ReadMe.txt +++ b/test/selenium/ReadMe.txt @@ -1,31 +1,41 @@ ############################################## + +Questions? Post'em @ dev@cloudstack.apache.org + +############################################## + This files contains following: 1) Installation requirements -2) Test Pre requisites -3) Running the Test and Generating the report +2) Testing pre-requisites +3) Running the Tests and Generating the report ############################################## ########################################################################################################################################## -1) Installtion Requirements +1) Installation Requirements +--------------------------- -1)Firefox depending on your OS (Good to have Firebug and Selenium IDE for troubleshooting and dev work) +1) Firefox depending on your OS (Good to have Firebug and Selenium IDE for troubleshooting and dev work) -2)Install Python 2.7. Recommend to use Active State Python +2) Install Python 2.7. 3) Now Open CMD/Terminal and type all of following -- pypm install pycrypto (Installs Pycrypto) -- pypm install paramiko (Install paramiko) +- pip install pycrypto (Installs Pycrypto) +- pip install paramiko (Install paramiko) - pip install unittest-xml-reporting (Install XML Test Runner) - pip install -U selenium (Installs Selenium) +4) Get PhoantomJS for your OS from http://phantomjs.org/ + +- PhantomJS will run selenium test in headless mode. Follow the instruction on PhantomJS.org. +- Make sure the executable is in PATH. (TIP: Drop it in Python27 folder :-)) 5) Now get the HTMLTestRunner for nice looking report generation. - http://tungwaiyip.info/software/HTMLTestRunner.html @@ -35,18 +45,22 @@ This files contains following: ########################################################################################################################################## 2) Test Prerequisites +--------------------- + +- Download and install CS. /cwiki.apache.org has links to Installation Guide and API reference. +- Log into the management server and Add a Zone. (Must be Advance Zone and Hypervisor type must be Xen) -- Download and install CS -- Log into the management server nad Add a Zone. (Must be Advance Zone and Hypervisor type must be Xen) ########################################################################################################################################## 3) Running the Test and Generating the report +--------------------------------------------- - Folder smoke contains main.py - main.py is the file where all the tests are serialized. - main.py supports HTML and XML reporting. Please refer to end of file to choose either. -- Typical usage is: python main.py for XML Reporting -- And python main.py >> results.html for HTML Reporting. +- Typical usage is: python main.py 10.1.1.10 >> result.xml for XML Reporting +- And python main.py 10.1.1.10 >> result.html for HTML Reporting. +- 10.1.1.10 (your management server IP) is an argument required for main. ########################################################################################################################################## diff --git a/test/selenium/lib/initialize.py b/test/selenium/lib/initialize.py index e8cc49adff4..55e5d9aa3b1 100644 --- a/test/selenium/lib/initialize.py +++ b/test/selenium/lib/initialize.py @@ -21,11 +21,26 @@ This will help pass webdriver (Browser instance) across our test cases. from selenium import webdriver +import sys DRIVER = None +MS_ip = None + def getOrCreateWebdriver(): global DRIVER - DRIVER = DRIVER or webdriver.Firefox() + DRIVER = DRIVER or webdriver.PhantomJS('phantomjs') # phantomjs executable must be in PATH. return DRIVER + +def getMSip(): + global MS_ip + if len(sys.argv) >= 3: + sys.exit("Only One argument is required .. Enter your Management Server IP") + + if len(sys.argv) == 1: + sys.exit("Atleast One argument is required .. Enter your Management Server IP") + + for arg in sys.argv[1:]: + MS_ip = arg + return MS_ip \ No newline at end of file diff --git a/test/selenium/smoke/Login_and_Accounts.py b/test/selenium/smoke/Login_and_Accounts.py index c5132d9754c..44b1e836b4c 100644 --- a/test/selenium/smoke/Login_and_Accounts.py +++ b/test/selenium/smoke/Login_and_Accounts.py @@ -34,11 +34,12 @@ class login(unittest.TestCase): def setUp(self): + MS_URL = initialize.getMSip() self.driver = initialize.getOrCreateWebdriver() - self.base_url = "http://10.223.49.206:8080/" # Your management Server IP goes here + self.base_url = "http://"+ MS_URL +":8080/" # Your management Server IP goes here self.verificationErrors = [] - + def test_login(self): # Here we will clear the test box for Username and Password and fill them with actual login data. diff --git a/test/selenium/smoke/main.py b/test/selenium/smoke/main.py index 86bb9308c2f..921192dc847 100644 --- a/test/selenium/smoke/main.py +++ b/test/selenium/smoke/main.py @@ -21,7 +21,7 @@ import xmlrunner global DRIVER - +global MS_ip # Import test cases From f1012410503edf4f7fd624ecf4f5004bea0ac9a1 Mon Sep 17 00:00:00 2001 From: Kelven Yang Date: Wed, 1 May 2013 17:40:23 -0700 Subject: [PATCH 18/47] Apply patch for https://reviews.apache.org/r/10892/ --- .../api/storage/CreateVolumeOVAAnswer.java | 26 ++ .../api/storage/CreateVolumeOVACommand.java | 60 +++ .../api/storage/PrepareOVAPackingAnswer.java | 26 ++ .../api/storage/PrepareOVAPackingCommand.java | 48 +++ .../user/snapshot/CreateSnapshotCmd.java | 2 + .../storage/resource/StoragePoolResource.java | 4 + .../com/cloud/hypervisor/guru/VMwareGuru.java | 12 + .../vmware/manager/VmwareStorageManager.java | 4 + .../manager/VmwareStorageManagerImpl.java | 403 +++++++++++++++--- .../vmware/resource/VmwareResource.java | 48 +++ ...VmwareSecondaryStorageResourceHandler.java | 24 ++ .../cloud/server/ManagementServerImpl.java | 17 +- .../template/HypervisorTemplateAdapter.java | 78 ++++ .../com/cloud/template/TemplateAdapter.java | 3 + .../cloud/template/TemplateAdapterBase.java | 13 + .../cloud/template/TemplateManagerImpl.java | 7 + .../vmware/mo/VirtualMachineMO.java | 6 +- 17 files changed, 725 insertions(+), 56 deletions(-) create mode 100755 api/src/com/cloud/agent/api/storage/CreateVolumeOVAAnswer.java create mode 100755 api/src/com/cloud/agent/api/storage/CreateVolumeOVACommand.java create mode 100755 api/src/com/cloud/agent/api/storage/PrepareOVAPackingAnswer.java create mode 100755 api/src/com/cloud/agent/api/storage/PrepareOVAPackingCommand.java diff --git a/api/src/com/cloud/agent/api/storage/CreateVolumeOVAAnswer.java b/api/src/com/cloud/agent/api/storage/CreateVolumeOVAAnswer.java new file mode 100755 index 00000000000..52a57dba7b2 --- /dev/null +++ b/api/src/com/cloud/agent/api/storage/CreateVolumeOVAAnswer.java @@ -0,0 +1,26 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +package com.cloud.agent.api.storage; + +import com.cloud.agent.api.Answer; + +public class CreateVolumeOVAAnswer extends Answer { + public CreateVolumeOVAAnswer(CreateVolumeOVACommand cmd, boolean result, String details) { + super(cmd, result, details); + } + +} diff --git a/api/src/com/cloud/agent/api/storage/CreateVolumeOVACommand.java b/api/src/com/cloud/agent/api/storage/CreateVolumeOVACommand.java new file mode 100755 index 00000000000..224b7c80d43 --- /dev/null +++ b/api/src/com/cloud/agent/api/storage/CreateVolumeOVACommand.java @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +package com.cloud.agent.api.storage; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.to.StorageFilerTO; +import com.cloud.storage.StoragePool; + +public class CreateVolumeOVACommand extends Command { + String secUrl; + String volPath; + String volName; + StorageFilerTO pool; + + public CreateVolumeOVACommand() { + } + + public CreateVolumeOVACommand(String secUrl, String volPath, String volName, StoragePool pool, int wait) { + this.secUrl = secUrl; + this.volPath = volPath; + this.volName = volName; + this.pool = new StorageFilerTO(pool); + setWait(wait); + } + + @Override + public boolean executeInSequence() { + return true; + } + + public String getVolPath() { + return this.volPath; + } + + public String getVolName() { + return this.volName; + } + public String getSecondaryStorageUrl() { + return this.secUrl; + } + public StorageFilerTO getPool() { + return pool; + } +} + + diff --git a/api/src/com/cloud/agent/api/storage/PrepareOVAPackingAnswer.java b/api/src/com/cloud/agent/api/storage/PrepareOVAPackingAnswer.java new file mode 100755 index 00000000000..923d952a137 --- /dev/null +++ b/api/src/com/cloud/agent/api/storage/PrepareOVAPackingAnswer.java @@ -0,0 +1,26 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +package com.cloud.agent.api.storage; + +import com.cloud.agent.api.Answer; + +public class PrepareOVAPackingAnswer extends Answer { + public PrepareOVAPackingAnswer(PrepareOVAPackingCommand cmd, boolean result, String details) { + super(cmd, result, details); + } + +} diff --git a/api/src/com/cloud/agent/api/storage/PrepareOVAPackingCommand.java b/api/src/com/cloud/agent/api/storage/PrepareOVAPackingCommand.java new file mode 100755 index 00000000000..29fa26d0bd0 --- /dev/null +++ b/api/src/com/cloud/agent/api/storage/PrepareOVAPackingCommand.java @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +package com.cloud.agent.api.storage; + +import com.cloud.agent.api.Command; + +public class PrepareOVAPackingCommand extends Command { + private String templatePath; + private String secUrl; + + public PrepareOVAPackingCommand() { + } + + public PrepareOVAPackingCommand(String secUrl, String templatePath) { + this.secUrl = secUrl; + this.templatePath = templatePath; + } + + @Override + public boolean executeInSequence() { + return true; + } + + public String getTemplatePath() { + return this.templatePath; + } + + public String getSecondaryStorageUrl() { + return this.secUrl; + } + +} + + \ No newline at end of file diff --git a/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java b/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java index 95d76599f70..0b33f568d8a 100644 --- a/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java @@ -163,6 +163,7 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd { @Override public void execute() { + s_logger.info("VOLSS: createSnapshotCmd starts:" + System.currentTimeMillis()); UserContext.current().setEventDetails("Volume Id: "+getVolumeId()); Snapshot snapshot = _snapshotService.createSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId())); if (snapshot != null) { @@ -172,6 +173,7 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd { } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot due to an internal error creating snapshot for volume " + volumeId); } + s_logger.info("VOLSS: backupSnapshotCmd finishes:" + System.currentTimeMillis()); } diff --git a/core/src/com/cloud/storage/resource/StoragePoolResource.java b/core/src/com/cloud/storage/resource/StoragePoolResource.java index 5d352b0b651..fccfd0f5784 100644 --- a/core/src/com/cloud/storage/resource/StoragePoolResource.java +++ b/core/src/com/cloud/storage/resource/StoragePoolResource.java @@ -21,6 +21,8 @@ import com.cloud.agent.api.storage.CopyVolumeAnswer; import com.cloud.agent.api.storage.CopyVolumeCommand; import com.cloud.agent.api.storage.CreateAnswer; import com.cloud.agent.api.storage.CreateCommand; +import com.cloud.agent.api.storage.CreateVolumeOVAAnswer; +import com.cloud.agent.api.storage.CreateVolumeOVACommand; import com.cloud.agent.api.storage.DestroyCommand; import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer; import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; @@ -34,5 +36,7 @@ public interface StoragePoolResource { CopyVolumeAnswer execute(CopyVolumeCommand cmd); + CreateVolumeOVAAnswer execute(CreateVolumeOVACommand cmd); + CreateAnswer execute(CreateCommand cmd); } diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/guru/VMwareGuru.java index ee1b3245126..55bb1e98366 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/guru/VMwareGuru.java @@ -38,6 +38,8 @@ import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; import com.cloud.agent.api.UnregisterVMCommand; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.CreateVolumeOVACommand; +import com.cloud.agent.api.storage.PrepareOVAPackingCommand; import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; @@ -282,10 +284,18 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru { cmd instanceof CreatePrivateTemplateFromVolumeCommand || cmd instanceof CreatePrivateTemplateFromSnapshotCommand || cmd instanceof CopyVolumeCommand || + cmd instanceof CreateVolumeOVACommand || + cmd instanceof PrepareOVAPackingCommand || cmd instanceof CreateVolumeFromSnapshotCommand) { needDelegation = true; } + /* Fang: remove this before checking in */ + // needDelegation = false; + if (cmd instanceof PrepareOVAPackingCommand || + cmd instanceof CreateVolumeOVACommand ) { + cmd.setContextParam("hypervisor", HypervisorType.VMware.toString()); + } if(needDelegation) { HostVO host = _hostDao.findById(hostId); assert(host != null); @@ -311,6 +321,8 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru { cmd instanceof CreatePrivateTemplateFromVolumeCommand || cmd instanceof CreatePrivateTemplateFromSnapshotCommand || cmd instanceof CopyVolumeCommand || + cmd instanceof CreateVolumeOVACommand || + cmd instanceof PrepareOVAPackingCommand || cmd instanceof CreateVolumeFromSnapshotCommand) { String workerName = _vmwareMgr.composeWorkerName(); diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java index a2e517d1fdb..8c0603e29e7 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManager.java @@ -25,6 +25,8 @@ import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; import com.cloud.agent.api.DeleteVMSnapshotCommand; import com.cloud.agent.api.RevertToVMSnapshotCommand; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.PrepareOVAPackingCommand; +import com.cloud.agent.api.storage.CreateVolumeOVACommand; import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; public interface VmwareStorageManager { @@ -33,6 +35,8 @@ public interface VmwareStorageManager { Answer execute(VmwareHostService hostService, CreatePrivateTemplateFromVolumeCommand cmd); Answer execute(VmwareHostService hostService, CreatePrivateTemplateFromSnapshotCommand cmd); Answer execute(VmwareHostService hostService, CopyVolumeCommand cmd); + Answer execute(VmwareHostService hostService, CreateVolumeOVACommand cmd); + Answer execute(VmwareHostService hostService, PrepareOVAPackingCommand cmd); Answer execute(VmwareHostService hostService, CreateVolumeFromSnapshotCommand cmd); Answer execute(VmwareHostService hostService, CreateVMSnapshotCommand cmd); Answer execute(VmwareHostService hostService, DeleteVMSnapshotCommand cmd); diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java index 1f116455761..9f1351e96f3 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java @@ -18,12 +18,14 @@ package com.cloud.hypervisor.vmware.manager; import java.io.BufferedWriter; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Properties; import java.util.Map; import java.util.UUID; @@ -44,6 +46,10 @@ import com.cloud.agent.api.RevertToVMSnapshotAnswer; import com.cloud.agent.api.RevertToVMSnapshotCommand; import com.cloud.agent.api.storage.CopyVolumeAnswer; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.PrepareOVAPackingAnswer; +import com.cloud.agent.api.storage.PrepareOVAPackingCommand; +import com.cloud.agent.api.storage.CreateVolumeOVAAnswer; +import com.cloud.agent.api.storage.CreateVolumeOVACommand; import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer; import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer; import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; @@ -102,6 +108,109 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { _timeout = NumbersUtil.parseInt(value, 1440) * 1000; } + //Fang note: use Answer here instead of the PrepareOVAPackingAnswer + @Override + public Answer execute(VmwareHostService hostService, PrepareOVAPackingCommand cmd) { + String secStorageUrl = ((PrepareOVAPackingCommand) cmd).getSecondaryStorageUrl(); + assert (secStorageUrl != null); + String installPath = cmd.getTemplatePath(); + String details = null; + boolean success = false; + String ovafileName = ""; + s_logger.info("Fang: execute OVAPacking cmd at vmwareMngImpl. "); + String secondaryMountPoint = _mountService.getMountPoint(secStorageUrl); + // String installPath = getTemplateRelativeDirInSecStorage(accountId, templateId); + String installFullPath = secondaryMountPoint + "/" + installPath; + + String templateName = installFullPath; // should be a file ending .ova; + s_logger.info("Fang: execute vmwareMgrImpl: templateNAme " + templateName); + // Fang: Dir list, if there is ova file, done; Fang: add answer cmd; + // if not, from ova.meta, create a new OVA file; + // change the install path to *.ova , not ova.meta; + // VmwareContext context = hostService.getServiceContext(cmd); //Fang: we may not have the CTX here + try { + if (templateName.endsWith(".ova")) { + if(new File(templateName).exists()) { + details = "OVA files exists. succeed. "; + return new Answer(cmd, true, details); + } else { + if (new File(templateName + ".meta").exists()) { //Fang parse the meta file + //execute the tar command; + s_logger.info("Fang: execute vmwareMgrImpl: getfromMeta " + templateName); + ovafileName = getOVAFromMetafile(templateName + ".meta"); + details = "OVA file in meta file is " + ovafileName; + return new Answer(cmd, true, details); + } else { + String msg = "Unable to find ova meta or ova file to prepare template (vmware)"; + s_logger.error(msg); + throw new Exception(msg); + } + } + } + } catch (Throwable e) { + if (e instanceof RemoteException) { + //hostService.invalidateServiceContext(context); do not need context + s_logger.error("Unable to connect to remote service "); + details = "Unable to connect to remote service "; + return new Answer(cmd, false, details); + } + String msg = "Unable to execute PrepareOVAPackingCommand due to exception"; + s_logger.error(msg, e); + return new Answer(cmd, false, details); + } + return new Answer(cmd, true, details); + } + + //Fang: new command added; + // Important! we need to sync file system before we can safely use tar to work around a linux kernal bug(or feature) + @Override + public Answer execute(VmwareHostService hostService, CreateVolumeOVACommand cmd) { + String secStorageUrl = ((CreateVolumeOVACommand) cmd).getSecondaryStorageUrl(); + assert (secStorageUrl != null); + String installPath = cmd.getVolPath(); + String details = null; + boolean success = false; + + s_logger.info("volss: execute CreateVolumeOVA cmd at vmwareMngImpl. "); + String secondaryMountPoint = _mountService.getMountPoint(secStorageUrl); + // String installPath = getTemplateRelativeDirInSecStorage(accountId, templateId); + s_logger.info("volss: mountPoint: " + secondaryMountPoint + "installPath:" + installPath); + String installFullPath = secondaryMountPoint + "/" + installPath; + + String volName = cmd.getVolName(); // should be a UUID, without ova ovf, etc; + s_logger.info("volss: execute vmwareMgrImpl: VolName " + volName); + // Fang: Dir list, if there is ova file, done; Note: add answer cmd; + + try { + if(new File(volName + ".ova").exists()) { + details = "OVA files exists. succeed. "; + return new CreateVolumeOVAAnswer(cmd, true, details); + } else { + File ovaFile = new File(installFullPath); + String exportDir = ovaFile.getParent(); + + s_logger.info("Fang: exportDir is (for VolumeOVA): " + exportDir); + s_logger.info("Sync file system before we package OVA..."); + + Script commandSync = new Script(true, "sync", 0, s_logger); + commandSync.execute(); + + Script command = new Script(false, "tar", 0, s_logger); + command.setWorkDir(exportDir); + command.add("-cf", volName + ".ova"); + command.add(volName + ".ovf"); // OVF file should be the first file in OVA archive + command.add(volName + "-disk0.vmdk"); + + s_logger.info("Package Volume OVA with commmand: " + command.toString()); + command.execute(); + return new CreateVolumeOVAAnswer(cmd, true, details); + } + } catch (Throwable e) { + s_logger.info("Exception for createVolumeOVA"); + } + return new CreateVolumeOVAAnswer(cmd, true, "fail to pack OVA for volume"); + } + @Override public Answer execute(VmwareHostService hostService, PrimaryStorageDownloadCommand cmd) { String secondaryStorageUrl = cmd.getSecondaryStorageUrl(); @@ -570,11 +679,14 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { String secondaryMountPoint = _mountService.getMountPoint(secStorageUrl); String installPath = getTemplateRelativeDirInSecStorage(accountId, templateId); String installFullPath = secondaryMountPoint + "/" + installPath; - String installFullName = installFullPath + "/" + templateUniqueName + ".ova"; - String snapshotFullName = secondaryMountPoint + "/" + getSnapshotRelativeDirInSecStorage(accountId, volumeId) - + "/" + backedUpSnapshotUuid + ".ova"; + String installFullOVAName = installFullPath + "/" + templateUniqueName + ".ova"; //Note: volss for tmpl + String snapshotRoot = secondaryMountPoint + "/" + getSnapshotRelativeDirInSecStorage(accountId, volumeId); + String snapshotFullOVAName = snapshotRoot + "/" + backedUpSnapshotUuid + ".ova"; + String snapshotFullOvfName = snapshotRoot + "/" + backedUpSnapshotUuid + ".ovf"; String result; Script command; + String templateVMDKName = ""; + String snapshotFullVMDKName = snapshotRoot + "/"; synchronized(installPath.intern()) { command = new Script(false, "mkdir", _timeout, s_logger); @@ -591,40 +703,85 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { } try { - command = new Script(false, "cp", _timeout, s_logger); - command.add(snapshotFullName); - command.add(installFullName); - result = command.execute(); - if(result != null) { - String msg = "unable to copy snapshot " + snapshotFullName + " to " + installFullPath; + if(new File(snapshotFullOVAName).exists()) { + command = new Script(false, "cp", _timeout, s_logger); + command.add(snapshotFullOVAName); + command.add(installFullOVAName); + result = command.execute(); + if(result != null) { + String msg = "unable to copy snapshot " + snapshotFullOVAName + " to " + installFullPath; + s_logger.error(msg); + throw new Exception(msg); + } + + // untar OVA file at template directory + command = new Script("tar", 0, s_logger); + command.add("--no-same-owner"); + command.add("-xf", installFullOVAName); + command.setWorkDir(installFullPath); + s_logger.info("Executing command: " + command.toString()); + result = command.execute(); + if(result != null) { + String msg = "unable to untar snapshot " + snapshotFullOVAName + " to " + + installFullPath; + s_logger.error(msg); + throw new Exception(msg); + } + + } else { // there is no ova file, only ovf originally; + if(new File(snapshotFullOvfName).exists()) { + command = new Script(false, "cp", _timeout, s_logger); + command.add(snapshotFullOvfName); + //command.add(installFullOvfName); + command.add(installFullPath); + result = command.execute(); + if(result != null) { + String msg = "unable to copy snapshot " + snapshotFullOvfName + " to " + installFullPath; + s_logger.error(msg); + throw new Exception(msg); + } + + File snapshotdir = new File(snapshotRoot); + File[] ssfiles = snapshotdir.listFiles(); + // List filenames = new ArrayList(); + for (int i = 0; i < ssfiles.length; i++) { + String vmdkfile = ssfiles[i].getName(); + if(vmdkfile.toLowerCase().startsWith(backedUpSnapshotUuid) && vmdkfile.toLowerCase().endsWith(".vmdk")) { + snapshotFullVMDKName += vmdkfile; + templateVMDKName += vmdkfile; + break; + } + } + if (snapshotFullVMDKName != null) { + command = new Script(false, "cp", _timeout, s_logger); + command.add(snapshotFullVMDKName); + command.add(installFullPath); + result = command.execute(); + s_logger.info("Copy VMDK file: " + snapshotFullVMDKName); + if(result != null) { + String msg = "unable to copy snapshot vmdk file " + snapshotFullVMDKName + " to " + installFullPath; + s_logger.error(msg); + throw new Exception(msg); + } + } + } else { + String msg = "unable to find any snapshot ova/ovf files" + snapshotFullOVAName + " to " + installFullPath; s_logger.error(msg); throw new Exception(msg); + } } - // untar OVA file at template directory - command = new Script("tar", 0, s_logger); - command.add("--no-same-owner"); - command.add("-xf", installFullName); - command.setWorkDir(installFullPath); - s_logger.info("Executing command: " + command.toString()); - result = command.execute(); - if(result != null) { - String msg = "unable to untar snapshot " + snapshotFullName + " to " - + installFullPath; - s_logger.error(msg); - throw new Exception(msg); - } - - long physicalSize = new File(installFullPath + "/" + templateUniqueName + ".ova").length(); + long physicalSize = new File(installFullPath + "/" + templateVMDKName).length(); VmdkProcessor processor = new VmdkProcessor(); + // long physicalSize = new File(installFullPath + "/" + templateUniqueName + ".ova").length(); Map params = new HashMap(); params.put(StorageLayer.InstanceConfigKey, _storage); processor.configure("VMDK Processor", params); long virtualSize = processor.getTemplateVirtualSize(installFullPath, templateUniqueName); postCreatePrivateTemplate(installFullPath, templateId, templateUniqueName, physicalSize, virtualSize); + writeMetaOvaForTemplate(installFullPath, backedUpSnapshotUuid + ".ovf", templateVMDKName, templateUniqueName, physicalSize); return new Ternary(installPath + "/" + templateUniqueName + ".ova", physicalSize, virtualSize); - } catch(Exception e) { // TODO, clean up left over files throw e; @@ -648,7 +805,8 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { out.newLine(); out.write("size=" + size); out.newLine(); - out.write("ova=true"); + //out.write("ova=true"); + out.write("ova=false"); //volss: the real ova file is not created out.newLine(); out.write("id=" + templateId); out.newLine(); @@ -670,6 +828,31 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { } } + private void writeMetaOvaForTemplate(String installFullPath, String ovfFilename, String vmdkFilename, + String templateName, long diskSize) throws Exception { + + // TODO a bit ugly here + BufferedWriter out = null; + try { + out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(installFullPath + "/" + templateName +".ova.meta"))); + out.write("ova.filename=" + templateName + ".ova"); + out.newLine(); + out.write("version=1.0"); + out.newLine(); + out.write("ovf=" + ovfFilename); + out.newLine(); + out.write("numDisks=1"); + out.newLine(); + out.write("disk1.name=" + vmdkFilename); + out.newLine(); + out.write("disk1.size=" + diskSize); + out.newLine(); + } finally { + if(out != null) + out.close(); + } + } + private String createVolumeFromSnapshot(VmwareHypervisorHost hyperHost, DatastoreMO primaryDsMo, String newVolumeName, long accountId, long volumeId, String secStorageUrl, String snapshotBackupUuid) throws Exception { @@ -688,23 +871,35 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { if (backupName.contains("/")){ snapshotDir = backupName.split("/")[0]; } - String srcFileName = getOVFFilePath(srcOVAFileName); - if(srcFileName == null) { - Script command = new Script("tar", 0, s_logger); - command.add("--no-same-owner"); - command.add("-xf", srcOVAFileName); - command.setWorkDir(secondaryMountPoint + "/" + secStorageDir + "/" + snapshotDir); - s_logger.info("Executing command: " + command.toString()); - String result = command.execute(); - if(result != null) { - String msg = "Unable to unpack snapshot OVA file at: " + srcOVAFileName; - s_logger.error(msg); - throw new Exception(msg); - } - } - srcFileName = getOVFFilePath(srcOVAFileName); - if(srcFileName == null) { + File ovafile = new File(srcOVAFileName); + String srcOVFFileName = secondaryMountPoint + "/" + secStorageDir + "/" + + backupName + ".ovf"; + File ovfFile = new File(srcOVFFileName); + // String srcFileName = getOVFFilePath(srcOVAFileName); + if (!ovfFile.exists()) { + srcOVFFileName = getOVFFilePath(srcOVAFileName); + if(srcOVFFileName == null && ovafile.exists() ) { // volss: ova file exists; o/w can't do tar + Script command = new Script("tar", 0, s_logger); + command.add("--no-same-owner"); + command.add("-xf", srcOVAFileName); + command.setWorkDir(secondaryMountPoint + "/" + secStorageDir + "/" + snapshotDir); + s_logger.info("Executing command: " + command.toString()); + String result = command.execute(); + if(result != null) { + String msg = "Unable to unpack snapshot OVA file at: " + srcOVAFileName; + s_logger.error(msg); + throw new Exception(msg); + } + } else { + String msg = "Unable to find snapshot OVA file at: " + srcOVAFileName; + s_logger.error(msg); + throw new Exception(msg); + } + + srcOVFFileName = getOVFFilePath(srcOVAFileName); + } + if(srcOVFFileName == null) { String msg = "Unable to locate OVF file in template package directory: " + srcOVAFileName; s_logger.error(msg); throw new Exception(msg); @@ -712,7 +907,7 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { VirtualMachineMO clonedVm = null; try { - hyperHost.importVmFromOVF(srcFileName, newVolumeName, primaryDsMo, "thin"); + hyperHost.importVmFromOVF(srcOVFFileName, newVolumeName, primaryDsMo, "thin"); clonedVm = hyperHost.findVmOnHyperHost(newVolumeName); if(clonedVm == null) throw new Exception("Unable to create container VM for volume creation"); @@ -774,7 +969,7 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { throw new Exception(msg); } - clonedVm.exportVm(exportPath, exportName, true, true); + clonedVm.exportVm(exportPath, exportName, false, false); //Note: volss: not to create ova. } finally { if(clonedVm != null) { clonedVm.detachAllDisks(); @@ -787,17 +982,31 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { String secondaryMountPoint = _mountService.getMountPoint(secStorageUrl); String snapshotMountRoot = secondaryMountPoint + "/" + getSnapshotRelativeDirInSecStorage(accountId, volumeId); - File file = new File(snapshotMountRoot + "/" + backupUuid + ".ova"); + File file = new File(snapshotMountRoot + "/" + backupUuid + ".ovf"); if(file.exists()) { - if(file.delete()) - return null; - - } else { - return "Backup file does not exist. backupUuid: " + backupUuid; - } - - return "Failed to delete snapshot backup file, backupUuid: " + backupUuid; - } + File snapshotdir = new File(snapshotMountRoot); + File[] ssfiles = snapshotdir.listFiles(); + // List filenames = new ArrayList(); + for (int i = 0; i < ssfiles.length; i++) { + String vmdkfile = ssfiles[i].getName(); + if(vmdkfile.toLowerCase().startsWith(backupUuid) && vmdkfile.toLowerCase().endsWith(".vmdk")) { + // filenames.add(vmdkfile); + new File(vmdkfile).delete(); + } + } + if(file.delete()) + return null; + } else { + File file1 = new File(snapshotMountRoot + "/" + backupUuid + ".ova"); + if(file1.exists()) { + if(file1.delete()) + return null; + } else { + return "Backup file does not exist. backupUuid: " + backupUuid; + } + } + return "Failed to delete snapshot backup file, backupUuid: " + backupUuid; + } private Pair copyVolumeToSecStorage(VmwareHostService hostService, VmwareHypervisorHost hyperHost, CopyVolumeCommand cmd, String vmName, long volumeId, String poolId, String volumePath, @@ -881,6 +1090,92 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { return new Pair(volumeFolder, newVolume); } + //Fang: here I use a method to return the ovf and vmdk file names; Another way to do it: + // create a new class, and like TemplateLocation.java and create templateOvfInfo.java to handle it; + private String getOVAFromMetafile(String metafileName) throws Exception { + File ova_metafile = new File(metafileName); + Properties props = null; + FileInputStream strm = null; + String ovaFileName = ""; + s_logger.info("Fang: getOVAfromMetaFile: metafileName " + metafileName); + try { + strm = new FileInputStream(ova_metafile); + if (null == strm) { + String msg = "Cannot read ova meat file. Error"; + s_logger.error(msg); + throw new Exception(msg); + } + + s_logger.info("Fang: getOVAfromMetaFile: load strm " ); + if (null != ova_metafile) { + props = new Properties(); + props.load(strm); + if (props == null) { + s_logger.info("Fang: getOVAfromMetaFile: props is null. " ); + } + } + if (null != props) { + ovaFileName = props.getProperty("ova.filename"); + s_logger.info("Fang: ovafilename" + ovaFileName); + String ovfFileName = props.getProperty("ovf"); + s_logger.info("Fang: ovffilename" + ovfFileName); + int diskNum = Integer.parseInt(props.getProperty("numDisks")); + if (diskNum <= 0) { + String msg = "VMDK disk file number is 0. Error"; + s_logger.error(msg); + throw new Exception(msg); + } + String[] disks = new String[diskNum]; + for (int i = 0; i < diskNum; i++) { + //String diskNameKey = "disk" + Integer.toString(i+1) + ".name"; // Fang use this + String diskNameKey = "disk1.name"; + disks[i] = props.getProperty(diskNameKey); + s_logger.info("Fang: diskname " + disks[i]); + } + String exportDir = ova_metafile.getParent(); + s_logger.info("Fang: exportDir: " + exportDir); + // Important! we need to sync file system before we can safely use tar to work around a linux kernal bug(or feature) + s_logger.info("Fang: Sync file system before we package OVA..., before tar "); + s_logger.info("Fang: ova: " + ovaFileName+ ", ovf:" + ovfFileName + ", vmdk:" + disks[0] + "."); + Script commandSync = new Script(true, "sync", 0, s_logger); + commandSync.execute(); + Script command = new Script(false, "tar", 0, s_logger); + command.setWorkDir(exportDir); //Fang: pass this in to the method? + command.add("-cf", ovaFileName); + command.add(ovfFileName); // OVF file should be the first file in OVA archive + for(String diskName: disks) { + command.add(diskName); + } + command.execute(); + s_logger.info("Fang: Package OVA for template in dir: " + exportDir + "cmd: " + command.toString()); + // to be safe, physically test existence of the target OVA file + if((new File(exportDir + ovaFileName)).exists()) { + s_logger.info("Fang: ova file is created and ready to extract "); + return (ovaFileName); + } else { + String msg = exportDir + File.separator + ovaFileName + ".ova is not created as expected"; + s_logger.error(msg); + throw new Exception(msg); + } + } else { + String msg = "Error reading the ova meta file: " + metafileName; + s_logger.error(msg); + throw new Exception(msg); + } + } catch (Exception e) { + return null; + //Do something, re-throw the exception + } finally { + if (strm != null) { + try { + strm.close(); + } catch (Exception e) { + } + } + } + + } + private String getOVFFilePath(String srcOVAFileName) { File file = new File(srcOVAFileName); assert(_storage != null); diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 99ad1ca60b2..38c9f86d9c3 100755 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -155,6 +155,10 @@ import com.cloud.agent.api.routing.VmDataCommand; import com.cloud.agent.api.routing.VpnUsersCfgCommand; import com.cloud.agent.api.storage.CopyVolumeAnswer; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.CreateVolumeOVACommand; +import com.cloud.agent.api.storage.CreateVolumeOVAAnswer; +import com.cloud.agent.api.storage.PrepareOVAPackingAnswer; +import com.cloud.agent.api.storage.PrepareOVAPackingCommand; import com.cloud.agent.api.storage.CreateAnswer; import com.cloud.agent.api.storage.CreateCommand; import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer; @@ -392,6 +396,10 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa answer = execute((DeleteStoragePoolCommand) cmd); } else if (clz == CopyVolumeCommand.class) { answer = execute((CopyVolumeCommand) cmd); + } else if (clz == CreateVolumeOVACommand.class) { + answer = execute((CreateVolumeOVACommand) cmd); + } else if (clz == PrepareOVAPackingCommand.class) { + answer = execute((PrepareOVAPackingCommand) cmd); } else if (clz == AttachVolumeCommand.class) { answer = execute((AttachVolumeCommand) cmd); } else if (clz == AttachIsoCommand.class) { @@ -3905,8 +3913,48 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa } } + public CreateVolumeOVAAnswer execute(CreateVolumeOVACommand cmd) { + if (s_logger.isInfoEnabled()) { + s_logger.info("Executing resource CreateVolumeOVACommand: " + _gson.toJson(cmd)); + } + try { + VmwareContext context = getServiceContext(); + VmwareManager mgr = context.getStockObject(VmwareManager.CONTEXT_STOCK_NAME); + return (CreateVolumeOVAAnswer) mgr.getStorageManager().execute(this, cmd); + } catch (Throwable e) { + if (e instanceof RemoteException) { + s_logger.warn("Encounter remote exception to vCenter, invalidate VMware session context"); + invalidateServiceContext(); + } + String msg = "CreateVolumeOVACommand failed due to " + VmwareHelper.getExceptionMessage(e); + s_logger.error(msg, e); + return new CreateVolumeOVAAnswer(cmd, false, msg); + } + } + + protected Answer execute(PrepareOVAPackingCommand cmd) { + if (s_logger.isInfoEnabled()) { + s_logger.info("Executing resource PrepareOVAPackingCommand: " + _gson.toJson(cmd)); + } + + try { + VmwareContext context = getServiceContext(); + VmwareManager mgr = context.getStockObject(VmwareManager.CONTEXT_STOCK_NAME); + + return mgr.getStorageManager().execute(this, cmd); + } catch (Throwable e) { + if (e instanceof RemoteException) { + s_logger.warn("Encounter remote exception to vCenter, invalidate VMware session context"); + invalidateServiceContext(); + } + + String details = "PrepareOVAPacking for template failed due to " + VmwareHelper.getExceptionMessage(e); + s_logger.error(details, e); + return new PrepareOVAPackingAnswer(cmd, false, details); + } + } private boolean createVMFullClone(VirtualMachineMO vmTemplate, DatacenterMO dcMo, DatastoreMO dsMo, String vmdkName, ManagedObjectReference morDatastore, ManagedObjectReference morPool) throws Exception { diff --git a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareSecondaryStorageResourceHandler.java b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareSecondaryStorageResourceHandler.java index ce42f67bf1d..95ba317fa2c 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareSecondaryStorageResourceHandler.java +++ b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareSecondaryStorageResourceHandler.java @@ -28,6 +28,9 @@ import com.cloud.agent.api.CreatePrivateTemplateFromSnapshotCommand; import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.CreateVolumeOVAAnswer; +import com.cloud.agent.api.storage.CreateVolumeOVACommand; +import com.cloud.agent.api.storage.PrepareOVAPackingCommand; import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand; import com.cloud.hypervisor.vmware.manager.VmwareHostService; import com.cloud.hypervisor.vmware.manager.VmwareStorageManager; @@ -76,6 +79,10 @@ public class VmwareSecondaryStorageResourceHandler implements SecondaryStorageRe answer = execute((CreatePrivateTemplateFromSnapshotCommand)cmd); } else if(cmd instanceof CopyVolumeCommand) { answer = execute((CopyVolumeCommand)cmd); + } else if(cmd instanceof CreateVolumeOVACommand) { + answer = execute((CreateVolumeOVACommand)cmd); + } else if (cmd instanceof PrepareOVAPackingCommand) { + answer = execute((PrepareOVAPackingCommand)cmd); } else if(cmd instanceof CreateVolumeFromSnapshotCommand) { answer = execute((CreateVolumeFromSnapshotCommand)cmd); } else { @@ -138,6 +145,23 @@ public class VmwareSecondaryStorageResourceHandler implements SecondaryStorageRe return _storageMgr.execute(this, cmd); } + private Answer execute(PrepareOVAPackingCommand cmd) { + s_logger.info("Fang: VmwareSecStorageResourceHandler: exec cmd. cmd is " + cmd.toString()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Executing resource PrepareOVAPackingCommand: " + _gson.toJson(cmd)); + } + + return _storageMgr.execute(this, cmd); + } + + private CreateVolumeOVAAnswer execute(CreateVolumeOVACommand cmd) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Executing resource CreateVolumeOVACommand: " + _gson.toJson(cmd)); + } + + return (CreateVolumeOVAAnswer) _storageMgr.execute(this, cmd); + } + private Answer execute(CreateVolumeFromSnapshotCommand cmd) { if (s_logger.isDebugEnabled()) { s_logger.debug("Executing resource CreateVolumeFromSnapshotCommand: " + _gson.toJson(cmd)); diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index f18c9d592aa..da01b83f79f 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -95,6 +95,8 @@ import com.cloud.agent.api.GetVncPortAnswer; import com.cloud.agent.api.GetVncPortCommand; import com.cloud.agent.api.storage.CopyVolumeAnswer; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.CreateVolumeOVAAnswer; +import com.cloud.agent.api.storage.CreateVolumeOVACommand; import com.cloud.agent.manager.allocator.HostAllocator; import com.cloud.alert.Alert; import com.cloud.alert.AlertManager; @@ -3081,7 +3083,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe List extractURLList = _uploadDao.listByTypeUploadStatus(volumeId, Upload.Type.VOLUME, UploadVO.Status.DOWNLOAD_URL_CREATED); if (extractMode == Upload.Mode.HTTP_DOWNLOAD && extractURLList.size() > 0) { - return extractURLList.get(0).getId(); // If download url already + return extractURLList.get(0).getId(); // If download url already Note: volss // exists then return } else { UploadVO uploadJob = _uploadMonitor.createNewUploadEntry(sserver.getId(), volumeId, UploadVO.Status.COPY_IN_PROGRESS, Upload.Type.VOLUME, @@ -3133,6 +3135,19 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe } String volumeLocalPath = "volumes/" + volume.getId() + "/" + cvAnswer.getVolumePath() + "." + getFormatForPool(srcPool); + //Fang: volss, handle the ova special case; + if (getFormatForPool(srcPool) == "ova") { + CreateVolumeOVACommand cvOVACmd = new CreateVolumeOVACommand(secondaryStorageURL, volumeLocalPath, cvAnswer.getVolumePath(), srcPool, copyvolumewait); + CreateVolumeOVAAnswer OVAanswer = null; + + try { + cvOVACmd.setContextParam("hypervisor", HypervisorType.VMware.toString()); + OVAanswer = (CreateVolumeOVAAnswer) _storageMgr.sendToPool(srcPool, cvOVACmd); //Fang: for extract volume, create the ova file here; + + } catch (StorageUnavailableException e) { + s_logger.debug("Storage unavailable"); + } + } // Update the DB that volume is copied and volumePath uploadJob.setUploadState(UploadVO.Status.COPY_COMPLETE); uploadJob.setLastUpdated(new Date()); diff --git a/server/src/com/cloud/template/HypervisorTemplateAdapter.java b/server/src/com/cloud/template/HypervisorTemplateAdapter.java index 491900b44e6..322f32eacdf 100755 --- a/server/src/com/cloud/template/HypervisorTemplateAdapter.java +++ b/server/src/com/cloud/template/HypervisorTemplateAdapter.java @@ -30,6 +30,9 @@ import javax.inject.Inject; import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd; import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd; import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd; +import org.apache.cloudstack.api.command.user.template.ExtractTemplateCmd; +import com.cloud.agent.api.storage.PrepareOVAPackingCommand; +import com.cloud.agent.api.storage.PrepareOVAPackingAnswer; import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; import org.apache.cloudstack.engine.subsystem.api.storage.CommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; @@ -64,6 +67,10 @@ import com.cloud.utils.UriUtils; import com.cloud.utils.db.DB; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.UserVmVO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; + @Local(value=TemplateAdapter.class) public class HypervisorTemplateAdapter extends TemplateAdapterBase implements TemplateAdapter { @@ -188,6 +195,77 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase implements Te return template; } + @Override + public TemplateProfile prepareExtractTemplate(ExtractTemplateCmd extractcmd) { + TemplateProfile profile = super.prepareExtractTemplate(extractcmd); + VMTemplateVO template = (VMTemplateVO)profile.getTemplate(); + Long zoneId = profile.getZoneId(); + Long templateId = template.getId(); + + if (template.getHypervisorType() == HypervisorType.VMware) { + PrepareOVAPackingCommand cmd = null; + String zoneName=""; + List secondaryStorageHosts; + if (!template.isCrossZones() && zoneId != null) { + DataCenterVO zone = _dcDao.findById(zoneId); + zoneName = zone.getName(); + secondaryStorageHosts = _ssvmMgr.listSecondaryStorageHostsInOneZone(zoneId); + + s_logger.debug("Attempting to mark template host refs for template: " + template.getName() + " as destroyed in zone: " + zoneName); + + // Make sure the template is downloaded to all the necessary secondary storage hosts + + for (HostVO secondaryStorageHost : secondaryStorageHosts) { + long hostId = secondaryStorageHost.getId(); + List templateHostVOs = _tmpltHostDao.listByHostTemplate(hostId, templateId); + for (VMTemplateHostVO templateHostVO : templateHostVOs) { + if (templateHostVO.getDownloadState() == Status.DOWNLOAD_IN_PROGRESS) { + String errorMsg = "Please specify a template that is not currently being downloaded."; + s_logger.debug("Template: " + template.getName() + " is currently being downloaded to secondary storage host: " + secondaryStorageHost.getName() + "."); + throw new CloudRuntimeException(errorMsg); + } + String installPath = templateHostVO.getInstallPath(); + if (installPath != null) { + HostVO ssvmhost = _ssvmMgr.pickSsvmHost(secondaryStorageHost); + if( ssvmhost == null ) { + s_logger.warn("prepareOVAPacking (hyervisorTemplateAdapter): There is no secondary storage VM for secondary storage host " + secondaryStorageHost.getName()); + throw new CloudRuntimeException("PrepareExtractTemplate: can't locate ssvm for SecStorage Host."); + } + //Answer answer = _agentMgr.sendToSecStorage(secondaryStorageHost, new PrepareOVAPackingCommand(secondaryStorageHost.getStorageUrl(), installPath)); + cmd = new PrepareOVAPackingCommand(secondaryStorageHost.getStorageUrl(), installPath); + + if (cmd == null) { + s_logger.debug("Fang: PrepareOVAPacking cmd can't created. cmd is null ."); + throw new CloudRuntimeException("PrepareExtractTemplate: can't create a new cmd to packing ova."); + } else { + cmd.setContextParam("hypervisor", HypervisorType.VMware.toString()); + } + Answer answer = null; + s_logger.debug("Fang: PrepareOVAPAcking cmd, before send out. cmd: " + cmd.toString()); + try { + answer = _agentMgr.send(ssvmhost.getId(), cmd); + } catch (AgentUnavailableException e) { + s_logger.warn("Unable to packOVA for template: id: " + templateId + ", name " + ssvmhost.getName(), e); + } catch (OperationTimedoutException e) { + s_logger.warn("Unable to packOVA for template timeout. template id: " + templateId); + e.printStackTrace(); + } + + if (answer == null || !answer.getResult()) { + s_logger.debug("Failed to create OVA for template " + templateHostVO + " due to " + ((answer == null) ? "answer is null" : answer.getDetails())); + throw new CloudRuntimeException("PrepareExtractTemplate: Failed to create OVA for template extraction. "); + } + } + } + } + } else { + s_logger.debug("Failed to create OVA for template " + template + " due to zone non-existing."); + throw new CloudRuntimeException("PrepareExtractTemplate: Failed to create OVA for template extraction. "); + } + } + return profile; + } + @Override @DB public boolean delete(TemplateProfile profile) { boolean success = true; diff --git a/server/src/com/cloud/template/TemplateAdapter.java b/server/src/com/cloud/template/TemplateAdapter.java index 1f8f491cb25..9a2d877926d 100755 --- a/server/src/com/cloud/template/TemplateAdapter.java +++ b/server/src/com/cloud/template/TemplateAdapter.java @@ -22,6 +22,7 @@ import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd; import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd; import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd; import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; +import org.apache.cloudstack.api.command.user.template.ExtractTemplateCmd; import com.cloud.exception.ResourceAllocationException; import com.cloud.hypervisor.Hypervisor.HypervisorType; @@ -56,6 +57,8 @@ public interface TemplateAdapter extends Adapter { public TemplateProfile prepareDelete(DeleteIsoCmd cmd); + public TemplateProfile prepareExtractTemplate(ExtractTemplateCmd cmd); + public boolean delete(TemplateProfile profile); public TemplateProfile prepare(boolean isIso, Long userId, String name, String displayText, Integer bits, diff --git a/server/src/com/cloud/template/TemplateAdapterBase.java b/server/src/com/cloud/template/TemplateAdapterBase.java index d3fd165121e..0940d3e2af1 100755 --- a/server/src/com/cloud/template/TemplateAdapterBase.java +++ b/server/src/com/cloud/template/TemplateAdapterBase.java @@ -26,6 +26,7 @@ import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd; import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd; import org.apache.cloudstack.api.command.user.template.DeleteTemplateCmd; import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; +import org.apache.cloudstack.api.command.user.template.ExtractTemplateCmd; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreRole; @@ -340,6 +341,18 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat return new TemplateProfile(userId, template, zoneId); } + public TemplateProfile prepareExtractTemplate(ExtractTemplateCmd cmd) { + Long templateId = cmd.getId(); + Long userId = UserContext.current().getCallerUserId(); + Long zoneId = cmd.getZoneId(); + + VMTemplateVO template = _tmpltDao.findById(templateId.longValue()); + if (template == null) { + throw new InvalidParameterValueException("unable to find template with id " + templateId); + } + return new TemplateProfile(userId, template, zoneId); + } + public TemplateProfile prepareDelete(DeleteIsoCmd cmd) { Long templateId = cmd.getId(); Long userId = UserContext.current().getCallerUserId(); diff --git a/server/src/com/cloud/template/TemplateManagerImpl.java b/server/src/com/cloud/template/TemplateManagerImpl.java index 63c6f6e6be0..8b5ee3aac1b 100755 --- a/server/src/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/com/cloud/template/TemplateManagerImpl.java @@ -367,6 +367,13 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, String mode = cmd.getMode(); Long eventId = cmd.getStartEventId(); + VirtualMachineTemplate template = getTemplate(templateId); + if (template == null) { + throw new InvalidParameterValueException("unable to find template with id " + templateId); + } + TemplateAdapter adapter = getAdapter(template.getHypervisorType()); + TemplateProfile profile = adapter.prepareExtractTemplate(cmd); + // FIXME: async job needs fixing Long uploadId = extract(caller, templateId, url, zoneId, mode, eventId, false, null, _asyncMgr); if (uploadId != null){ diff --git a/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java b/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java index 4503349edf1..660d9632baa 100644 --- a/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java +++ b/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java @@ -1281,6 +1281,7 @@ public class VirtualMachineMO extends BaseMO { long totalBytesDownloaded = 0; List deviceUrls = leaseInfo.getDeviceUrl(); + s_logger.info("volss: copy vmdk and ovf file starts " + System.currentTimeMillis()); if(deviceUrls != null) { OvfFile[] ovfFiles = new OvfFile[deviceUrls.size()]; for (int i = 0; i < deviceUrls.size(); i++) { @@ -1328,7 +1329,7 @@ public class VirtualMachineMO extends BaseMO { // tar files into OVA if(packToOva) { - // Important! we need to sync file system before we can safely use tar to work around a linux kernal bug(or feature) + // Important! we need to sync file system before we can safely use tar to work around a linux kernal bug(or feature) s_logger.info("Sync file system before we package OVA..."); Script commandSync = new Script(true, "sync", 0, s_logger); @@ -1351,8 +1352,11 @@ public class VirtualMachineMO extends BaseMO { } else { s_logger.error(exportDir + File.separator + exportName + ".ova is not created as expected"); } + } else { + success = true; } } + s_logger.info("volss: copy vmdk and ovf file finishes " + System.currentTimeMillis()); } catch(Throwable e) { s_logger.error("Unexpected exception ", e); } finally { From f1c794de1bb913002b5316992e331700a1f822b5 Mon Sep 17 00:00:00 2001 From: Hongtu Zang Date: Wed, 24 Apr 2013 11:13:29 +0800 Subject: [PATCH 19/47] CLOUDSTACK-2160: fix bug add a huge size guest network will cause out of memory Signed-off-by: Mice Xia --- server/src/com/cloud/network/NetworkModelImpl.java | 9 +++------ server/src/com/cloud/network/NetworkServiceImpl.java | 7 ++----- utils/src/com/cloud/utils/net/NetUtils.java | 10 +++++++--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/server/src/com/cloud/network/NetworkModelImpl.java b/server/src/com/cloud/network/NetworkModelImpl.java index bd62886674f..7b712ea9781 100755 --- a/server/src/com/cloud/network/NetworkModelImpl.java +++ b/server/src/com/cloud/network/NetworkModelImpl.java @@ -1666,20 +1666,17 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel { List ips = _nicDao.listIpAddressInNetwork(network.getId()); List secondaryIps = _nicSecondaryIpDao.listSecondaryIpAddressInNetwork(network.getId()); ips.addAll(secondaryIps); - Set allPossibleIps = NetUtils.getAllIpsFromCidr(cidr[0], Integer.parseInt(cidr[1])); Set usedIps = new TreeSet(); - + for (String ip : ips) { if (requestedIp != null && requestedIp.equals(ip)) { s_logger.warn("Requested ip address " + requestedIp + " is already in use in network" + network); return null; } - + usedIps.add(NetUtils.ip2Long(ip)); } - if (usedIps.size() != 0) { - allPossibleIps.removeAll(usedIps); - } + Set allPossibleIps = NetUtils.getAllIpsFromCidr(cidr[0], Integer.parseInt(cidr[1]), usedIps); String gateway = network.getGateway(); if ((gateway != null) && (allPossibleIps.contains(NetUtils.ip2Long(gateway)))) diff --git a/server/src/com/cloud/network/NetworkServiceImpl.java b/server/src/com/cloud/network/NetworkServiceImpl.java index 2dcb47d0cd8..ae0e4212efe 100755 --- a/server/src/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/com/cloud/network/NetworkServiceImpl.java @@ -2066,9 +2066,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { protected Set getAvailableIps(Network network, String requestedIp) { String[] cidr = network.getCidr().split("/"); List ips = _nicDao.listIpAddressInNetwork(network.getId()); - Set allPossibleIps = NetUtils.getAllIpsFromCidr(cidr[0], Integer.parseInt(cidr[1])); Set usedIps = new TreeSet(); - + for (String ip : ips) { if (requestedIp != null && requestedIp.equals(ip)) { s_logger.warn("Requested ip address " + requestedIp + " is already in use in network" + network); @@ -2077,9 +2076,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { usedIps.add(NetUtils.ip2Long(ip)); } - if (usedIps.size() != 0) { - allPossibleIps.removeAll(usedIps); - } + Set allPossibleIps = NetUtils.getAllIpsFromCidr(cidr[0], Integer.parseInt(cidr[1]), usedIps); String gateway = network.getGateway(); if ((gateway != null) && (allPossibleIps.contains(NetUtils.ip2Long(gateway)))) diff --git a/utils/src/com/cloud/utils/net/NetUtils.java b/utils/src/com/cloud/utils/net/NetUtils.java index 5988dd5f337..9f28d5b36b2 100755 --- a/utils/src/com/cloud/utils/net/NetUtils.java +++ b/utils/src/com/cloud/utils/net/NetUtils.java @@ -627,7 +627,7 @@ public class NetUtils { return result; } - public static Set getAllIpsFromCidr(String cidr, long size) { + public static Set getAllIpsFromCidr(String cidr, long size, Set usedIps) { assert (size < 32) : "You do know this is not for ipv6 right? Keep it smaller than 32 but you have " + size; Set result = new TreeSet(); long ip = ip2Long(cidr); @@ -639,8 +639,12 @@ public class NetUtils { end++; end = (end << (32 - size)) - 2; - while (start <= end) { - result.add(start); + int maxIps = 255; // get 255 ips as maximum + while (start <= end && maxIps > 0) { + if (!usedIps.contains(start)){ + result.add(start); + maxIps--; + } start++; } From 16865014a05ab04bb608a8122a7229b2b0ae3421 Mon Sep 17 00:00:00 2001 From: Rajesh Battala Date: Wed, 17 Apr 2013 18:22:30 +0530 Subject: [PATCH 20/47] CLOUDSTACK-1851 Health Check monitor not getting created on Netscaler device in Basic zone setup. --- .../network/element/NetscalerElement.java | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/plugins/network-elements/netscaler/src/com/cloud/network/element/NetscalerElement.java b/plugins/network-elements/netscaler/src/com/cloud/network/element/NetscalerElement.java index 2bbdb0450be..7bd9c2ec8b3 100644 --- a/plugins/network-elements/netscaler/src/com/cloud/network/element/NetscalerElement.java +++ b/plugins/network-elements/netscaler/src/com/cloud/network/element/NetscalerElement.java @@ -19,6 +19,8 @@ package com.cloud.network.element; import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; import com.cloud.agent.api.routing.GlobalLoadBalancerConfigCommand; +import com.cloud.agent.api.routing.HealthCheckLBConfigAnswer; +import com.cloud.agent.api.routing.HealthCheckLBConfigCommand; import com.cloud.agent.api.routing.LoadBalancerConfigCommand; import com.cloud.agent.api.routing.SetStaticNatRulesAnswer; import com.cloud.agent.api.routing.SetStaticNatRulesCommand; @@ -682,7 +684,7 @@ public class NetscalerElement extends ExternalLoadBalancerDeviceManagerImpl impl List destinations = rule.getDestinations(); if ((destinations != null && !destinations.isEmpty()) || rule.isAutoScaleConfig()) { - LoadBalancerTO loadBalancer = new LoadBalancerTO(lbUuid, srcIp, srcPort, protocol, algorithm, revoked, false, false, destinations, rule.getStickinessPolicies()); + LoadBalancerTO loadBalancer = new LoadBalancerTO(lbUuid, srcIp, srcPort, protocol, algorithm, revoked, false, false, destinations, rule.getStickinessPolicies(), rule.getHealthCheckPolicies()); if (rule.isAutoScaleConfig()) { loadBalancer.setAutoScaleVmGroup(rule.getAutoScaleVmGroup()); } @@ -808,11 +810,75 @@ public class NetscalerElement extends ExternalLoadBalancerDeviceManagerImpl impl return null; } + public List getElasticLBRulesHealthCheck(Network network, List rules) + throws ResourceUnavailableException { + + HealthCheckLBConfigAnswer answer = null; + List loadBalancingRules = new ArrayList(); + for (FirewallRule rule : rules) { + if (rule.getPurpose().equals(Purpose.LoadBalancing)) { + loadBalancingRules.add((LoadBalancingRule) rule); + } + } + + if (loadBalancingRules == null || loadBalancingRules.isEmpty()) { + return null; + } + + String errMsg = null; + ExternalLoadBalancerDeviceVO lbDeviceVO = getExternalLoadBalancerForNetwork(network); + + if (lbDeviceVO == null) { + s_logger.warn("There is no external load balancer device assigned to this network either network is not implement are already shutdown so just returning"); + return null; + } + + if (!isNetscalerDevice(lbDeviceVO.getDeviceName())) { + errMsg = "There are no NetScaler load balancer assigned for this network. So NetScaler element can not be handle elastic load balancer rules."; + s_logger.error(errMsg); + throw new ResourceUnavailableException(errMsg, this.getClass(), 0); + } + + List loadBalancersToApply = new ArrayList(); + for (int i = 0; i < loadBalancingRules.size(); i++) { + LoadBalancingRule rule = loadBalancingRules.get(i); + boolean revoked = (rule.getState().equals(FirewallRule.State.Revoke)); + String protocol = rule.getProtocol(); + String algorithm = rule.getAlgorithm(); + String lbUuid = rule.getUuid(); + String srcIp = _networkMgr.getIp(rule.getSourceIpAddressId()).getAddress().addr(); + int srcPort = rule.getSourcePortStart(); + List destinations = rule.getDestinations(); + + if ((destinations != null && !destinations.isEmpty()) || rule.isAutoScaleConfig()) { + LoadBalancerTO loadBalancer = new LoadBalancerTO(lbUuid, srcIp, srcPort, protocol, algorithm, revoked, + false, false, destinations, null, rule.getHealthCheckPolicies()); + loadBalancersToApply.add(loadBalancer); + } + } + + if (loadBalancersToApply.size() > 0) { + int numLoadBalancersForCommand = loadBalancersToApply.size(); + LoadBalancerTO[] loadBalancersForCommand = loadBalancersToApply + .toArray(new LoadBalancerTO[numLoadBalancersForCommand]); + HealthCheckLBConfigCommand cmd = new HealthCheckLBConfigCommand(loadBalancersForCommand); + HostVO externalLoadBalancer = _hostDao.findById(lbDeviceVO.getHostId()); + answer = (HealthCheckLBConfigAnswer) _agentMgr.easySend(externalLoadBalancer.getId(), cmd); + return answer.getLoadBalancers(); + } + return null; + } + public List updateHealthChecks(Network network, List lbrules) { if (canHandle(network, Service.Lb)) { try { - return getLBHealthChecks(network, lbrules); + + if (isBasicZoneNetwok(network)) { + return getElasticLBRulesHealthCheck(network, lbrules); + } else { + return getLBHealthChecks(network, lbrules); + } } catch (ResourceUnavailableException e) { s_logger.error("Error in getting the LB Rules from NetScaler " + e); } From 101d89c3a351c85c8f76bf7b1a723a1153a7be70 Mon Sep 17 00:00:00 2001 From: Radhika PC Date: Thu, 2 May 2013 11:43:02 +0530 Subject: [PATCH 21/47] CLOUDSTACK-2301 --- docs/en-US/elastic-ip.xml | 47 ++++++++++++++++--------- docs/en-US/images/eip-ns-basiczone.png | Bin 55568 -> 63227 bytes 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/docs/en-US/elastic-ip.xml b/docs/en-US/elastic-ip.xml index b09d37d43e1..8ecbd75be70 100644 --- a/docs/en-US/elastic-ip.xml +++ b/docs/en-US/elastic-ip.xml @@ -21,16 +21,31 @@

About Elastic IP Elastic IP (EIP) addresses are the IP addresses that are associated with an account, and act - as static IP addresses. The account owner has complete control over the Elastic IP addresses - that belong to the account. You can allocate an Elastic IP to a VM of your choice from the EIP - pool of your account. Later if required you can reassign the IP address to a different VM. This - feature is extremely helpful during VM failure. Instead of replacing the VM which is down, the - IP address can be reassigned to a new VM in your account. Elastic IP service provides Static NAT - (1:1) service in an EIP-enabled basic zone. The default network offering, + as static IP addresses. The account owner has the complete control over the Elastic IP addresses + that belong to the account. As an account owner, you can allocate an Elastic IP to a VM of your + choice from the EIP pool of your account. Later if required you can reassign the IP address to a + different VM. This feature is extremely helpful during VM failure. Instead of replacing the VM + which is down, the IP address can be reassigned to a new VM in your account. + Similar to the public IP address, Elastic IP addresses are mapped to their associated + private IP addresses by using StaticNAT. The EIP service is equipped with StaticNAT (1:1) + service in an EIP-enabled basic zone. The default network offering, DefaultSharedNetscalerEIPandELBNetworkOffering, provides your network with EIP and ELB network - services if a NetScaler device is deployed in your zone. Similar to the public IP address, - Elastic IP addresses are also mapped to their associated private IP addresses by using Stactic - NAT. + services if a NetScaler device is deployed in your zone. Consider the following illustration for + more details. + + + + + + eip-ns-basiczone.png: Elastic IP in a NetScaler-enabled Basic Zone. + + + In the illustration, a NetScaler appliance is the default entry or exit point for the + &PRODUCT; instances, and firewall is the default entry or exit point for the rest of the data + center. Netscaler provides LB services and staticNAT service to the guest networks. The guest + traffic in the pods and the Management Server are on different subnets / VLANs. The policy-based + routing in the data center core switch sends the public traffic through the NetScaler, whereas + the rest of the data center goes through the firewall. The EIP work flow is as follows: @@ -48,7 +63,6 @@ supported by NetScaler, in which the source IP address is replaced in the packets generated by a VM in the private network with the public IP address. - This default public IP will be released in two cases: @@ -68,12 +82,12 @@ - However, for the deployments where public IPs are limited resources, you have the - flexibility to choose not to allocate a public IP by default. You can use the Associate Public - IP option to turn on or off the automatic public IP assignment in the EIP-enabled Basic zones. - If you turn off the automatic public IP assignment while creating a network offering, only a - private IP is assigned to a VM when the VM is deployed with that network offering. Later, the - user can acquire an IP for the VM and enable static NAT. + For the deployments where public IPs are limited resources, you have the flexibility to + choose not to allocate a public IP by default. You can use the Associate Public IP option to + turn on or off the automatic public IP assignment in the EIP-enabled Basic zones. If you turn + off the automatic public IP assignment while creating a network offering, only a private IP is + assigned to a VM when the VM is deployed with that network offering. Later, the user can acquire + an IP for the VM and enable static NAT. For more information on the Associate Public IP option, see . For more information on the Associate Public IP option, see the @@ -83,7 +97,6 @@ continue to get both public IP and private by default, irrespective of the network offering configuration. - New deployments which use the default shared network offering with EIP and ELB services to create a shared network in the Basic zone will continue allocating public IPs to each user VM. diff --git a/docs/en-US/images/eip-ns-basiczone.png b/docs/en-US/images/eip-ns-basiczone.png index 315ff55dab9e4169559ab3d357ffc4486d8c25de..bc88570531ab5117cc8b128714872506b8b826d0 100644 GIT binary patch literal 63227 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 z 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 b;{QMp-xzl+SAb=O0{kh+yq2z%Gzs`Whq0Rt literal 55568 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 From 1f1467974f77901bcde0c1c52ac4286185dcc333 Mon Sep 17 00:00:00 2001 From: Jayapal Date: Thu, 2 May 2013 15:24:21 +0530 Subject: [PATCH 22/47] CLOUDSTACK-1828 Source Nat on private gateway feature --- api/src/com/cloud/network/NetworkService.java | 4 +++- api/src/com/cloud/network/vpc/PrivateIp.java | 1 + api/src/com/cloud/network/vpc/VpcGateway.java | 4 ++++ api/src/com/cloud/network/vpc/VpcService.java | 4 +++- .../admin/vpc/CreatePrivateGatewayCmd.java | 14 +++++++++++++- .../api/response/PrivateGatewayResponse.java | 10 ++++++++++ .../VirtualRoutingResource.java | 15 ++++++++++++++- .../opt/cloud/bin/vpc_privateGateway.sh | 2 +- .../resource/LibvirtComputingResource.java | 2 +- .../vmware/resource/VmwareResource.java | 19 +++++++++++++++++++ .../xen/resource/CitrixResourceBase.java | 14 ++++++++++++++ .../src/com/cloud/api/ApiResponseHelper.java | 1 + .../com/cloud/network/NetworkServiceImpl.java | 6 +++--- ...VpcVirtualNetworkApplianceManagerImpl.java | 4 ++-- .../network/vpc/PrivateGatewayProfile.java | 5 +++++ .../cloud/network/vpc/PrivateIpAddress.java | 7 +++++++ .../com/cloud/network/vpc/PrivateIpVO.java | 12 ++++++++++-- .../com/cloud/network/vpc/VpcGatewayVO.java | 16 +++++++++++++--- .../com/cloud/network/vpc/VpcManagerImpl.java | 8 ++++---- .../cloud/network/MockNetworkManagerImpl.java | 12 ++---------- .../com/cloud/vpc/MockNetworkManagerImpl.java | 12 ++---------- .../com/cloud/vpc/MockVpcManagerImpl.java | 2 +- setup/db/db/schema-410to420.sql | 3 +++ 23 files changed, 136 insertions(+), 41 deletions(-) diff --git a/api/src/com/cloud/network/NetworkService.java b/api/src/com/cloud/network/NetworkService.java index 6c9bebc1c15..bea92dc2481 100755 --- a/api/src/com/cloud/network/NetworkService.java +++ b/api/src/com/cloud/network/NetworkService.java @@ -138,6 +138,7 @@ public interface NetworkService { ResourceAllocationException, ResourceUnavailableException, ConcurrentOperationException; /** + * * @param networkName * @param displayText * @param physicalNetworkId @@ -148,13 +149,14 @@ public interface NetworkService { * @param netmask * @param networkOwnerId * @param vpcId TODO + * @param sourceNat * @return * @throws InsufficientCapacityException * @throws ConcurrentOperationException * @throws ResourceAllocationException */ Network createPrivateNetwork(String networkName, String displayText, long physicalNetworkId, String vlan, - String startIp, String endIP, String gateway, String netmask, long networkOwnerId, Long vpcId) + String startIp, String endIP, String gateway, String netmask, long networkOwnerId, Long vpcId, Boolean sourceNat) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException; /* Requests an IP address for the guest nic */ diff --git a/api/src/com/cloud/network/vpc/PrivateIp.java b/api/src/com/cloud/network/vpc/PrivateIp.java index 857fc226f30..eb6843339c5 100644 --- a/api/src/com/cloud/network/vpc/PrivateIp.java +++ b/api/src/com/cloud/network/vpc/PrivateIp.java @@ -44,5 +44,6 @@ public interface PrivateIp { String getMacAddress(); long getNetworkId(); + boolean getSourceNat(); } diff --git a/api/src/com/cloud/network/vpc/VpcGateway.java b/api/src/com/cloud/network/vpc/VpcGateway.java index 17566160ec3..e3530d08561 100644 --- a/api/src/com/cloud/network/vpc/VpcGateway.java +++ b/api/src/com/cloud/network/vpc/VpcGateway.java @@ -77,4 +77,8 @@ public interface VpcGateway extends Identity, ControlledEntity, InternalIdentity * @return */ State getState(); + /** + * @return + */ + boolean getSourceNat(); } diff --git a/api/src/com/cloud/network/vpc/VpcService.java b/api/src/com/cloud/network/vpc/VpcService.java index 07ce89b0a3f..23e276489c2 100644 --- a/api/src/com/cloud/network/vpc/VpcService.java +++ b/api/src/com/cloud/network/vpc/VpcService.java @@ -163,6 +163,7 @@ public interface VpcService { /** * Persists VPC private gateway in the Database. * + * * @param vpcId TODO * @param physicalNetworkId * @param vlan @@ -170,13 +171,14 @@ public interface VpcService { * @param gateway * @param netmask * @param gatewayOwnerId + * @param isSourceNat * @return * @throws InsufficientCapacityException * @throws ConcurrentOperationException * @throws ResourceAllocationException */ public PrivateGateway createVpcPrivateGateway(long vpcId, Long physicalNetworkId, String vlan, String ipAddress, - String gateway, String netmask, long gatewayOwnerId) throws ResourceAllocationException, + String gateway, String netmask, long gatewayOwnerId, Boolean isSourceNat) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException; /** diff --git a/api/src/org/apache/cloudstack/api/command/admin/vpc/CreatePrivateGatewayCmd.java b/api/src/org/apache/cloudstack/api/command/admin/vpc/CreatePrivateGatewayCmd.java index 9fd736f8543..20556957ff2 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/vpc/CreatePrivateGatewayCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/vpc/CreatePrivateGatewayCmd.java @@ -69,6 +69,11 @@ public class CreatePrivateGatewayCmd extends BaseAsyncCreateCmd { required=true, description="the VPC network belongs to") private Long vpcId; + @Parameter(name=ApiConstants.SOURCE_NAT_SUPPORTED, type=CommandType.BOOLEAN, required=false, + description="source NAT supported value. Default value false. If 'true' source NAT is enabled on the private gateway" + + " 'false': sourcenat is not supported") + private Boolean isSourceNat; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -97,6 +102,13 @@ public class CreatePrivateGatewayCmd extends BaseAsyncCreateCmd { return vpcId; } + public Boolean getIsSourceNat () { + if (isSourceNat == null) { + return false; + } + return true; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -111,7 +123,7 @@ public class CreatePrivateGatewayCmd extends BaseAsyncCreateCmd { PrivateGateway result = null; try { result = _vpcService.createVpcPrivateGateway(getVpcId(), getPhysicalNetworkId(), - getVlan(), getStartIp(), getGateway(), getNetmask(), getEntityOwnerId()); + getVlan(), getStartIp(), getGateway(), getNetmask(), getEntityOwnerId(), getIsSourceNat()); } catch (InsufficientCapacityException ex){ s_logger.info(ex); s_logger.trace(ex); diff --git a/api/src/org/apache/cloudstack/api/response/PrivateGatewayResponse.java b/api/src/org/apache/cloudstack/api/response/PrivateGatewayResponse.java index 4123477dbfe..ca760626324 100644 --- a/api/src/org/apache/cloudstack/api/response/PrivateGatewayResponse.java +++ b/api/src/org/apache/cloudstack/api/response/PrivateGatewayResponse.java @@ -76,6 +76,10 @@ public class PrivateGatewayResponse extends BaseResponse implements ControlledEn private String state; + @SerializedName(ApiConstants.SOURCE_NAT_SUPPORTED) @Param(description = "Souce Nat enable status") + private Boolean sourceNat; + + @Override public String getObjectId() { return this.id; @@ -145,5 +149,11 @@ public class PrivateGatewayResponse extends BaseResponse implements ControlledEn public void setState(String state) { this.state = state; } + + public void setSourceNat(Boolean sourceNat) { + this.sourceNat = sourceNat; + } + + } diff --git a/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java b/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java index 7148e0710ca..b9bda4d9688 100755 --- a/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java +++ b/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java @@ -863,13 +863,16 @@ public class VirtualRoutingResource implements Manager { } public void assignVpcIpToRouter(final String routerIP, final boolean add, final String pubIP, - final String nicname, final String gateway, final String netmask, final String subnet) throws InternalErrorException { + final String nicname, final String gateway, final String netmask, final String subnet, boolean sourceNat) throws InternalErrorException { String args = ""; + String snatArgs = ""; if (add) { args += " -A "; + snatArgs += " -A "; } else { args += " -D "; + snatArgs += " -D "; } args += " -l "; @@ -887,6 +890,16 @@ public class VirtualRoutingResource implements Manager { if (result != null) { throw new InternalErrorException("KVM plugin \"vpc_ipassoc\" failed:"+result); } + if (sourceNat) { + snatArgs += " -l " + pubIP; + snatArgs += " -c " + nicname; + + result = routerProxy("vpc_privateGateway.sh", routerIP, snatArgs); + if (result != null) { + throw new InternalErrorException("KVM plugin \"vpc_privateGateway\" failed:"+result); + } + + } } private SetStaticRouteAnswer execute(SetStaticRouteCommand cmd) { diff --git a/patches/systemvm/debian/config/opt/cloud/bin/vpc_privateGateway.sh b/patches/systemvm/debian/config/opt/cloud/bin/vpc_privateGateway.sh index a09d8f7e38b..3635e1cd44c 100755 --- a/patches/systemvm/debian/config/opt/cloud/bin/vpc_privateGateway.sh +++ b/patches/systemvm/debian/config/opt/cloud/bin/vpc_privateGateway.sh @@ -91,7 +91,7 @@ fi if [ "$Dflag" == "1" ] then - remove_sat $publicIp + remove_snat $publicIp unlock_exit $? $lock $locked fi diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 0064edf6a68..8fe8c88d0f8 100755 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -1756,7 +1756,7 @@ ServerResource { String netmask = Long.toString(NetUtils.getCidrSize(ip.getVlanNetmask())); String subnet = NetUtils.getSubNet(ip.getPublicIp(), ip.getVlanNetmask()); _virtRouterResource.assignVpcIpToRouter(routerIP, ip.isAdd(), ip.getPublicIp(), - nicName, ip.getVlanGateway(), netmask, subnet); + nicName, ip.getVlanGateway(), netmask, subnet, ip.isSourceNat()); results[i++] = ip.getPublicIp() + " - success"; } diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 38c9f86d9c3..28a54e1e411 100755 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -1441,10 +1441,14 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa } String args = ""; + String snatArgs = ""; + if (ip.isAdd()) { args += " -A "; + snatArgs += " -A "; } else { args += " -D "; + snatArgs += " -D "; } args += " -l "; @@ -1468,6 +1472,21 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa if (!result.first()) { throw new InternalErrorException("Unable to assign public IP address"); } + + if (ip.isSourceNat()) { + snatArgs += " -l "; + snatArgs += ip.getPublicIp(); + snatArgs += " -c "; + snatArgs += "eth" + ethDeviceNum; + + Pair result = SshHelper.sshExecute(routerIp, DEFAULT_DOMR_SSHPORT, "root", mgr.getSystemVMKeyFile(), null, + "/opt/cloud/bin/vpc_privateGateway.sh " + args); + + if (!result.first()) { + throw new InternalErrorException("Unable to assign public IP address"); + } + + } } protected void assignPublicIpAddress(VirtualMachineMO vmMo, final String vmName, final String privateIpAddress, final String publicIpAddress, final boolean add, final boolean firstIP, diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index 46ae35a4a54..bac361d9133 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -2217,11 +2217,14 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe } String args = "vpc_ipassoc.sh " + routerIp; + String snatArgs = "vpc_privateGateway.sh " + routerIp; if (ip.isAdd()) { args += " -A "; + snatArgs += " -A "; } else { args += " -D "; + snatArgs+= " -D "; } args += " -l "; @@ -2244,6 +2247,17 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe if (result == null || result.isEmpty()) { throw new InternalErrorException("Xen plugin \"vpc_ipassoc\" failed."); } + + if (ip.isSourceNat()) { + snatArgs += " -l " + ip.getPublicIp(); + snatArgs += " -c " + "eth" + correctVif.getDevice(conn); + + result = callHostPlugin(conn, "vmops", "routerProxy", "args", snatArgs); + if (result == null || result.isEmpty()) { + throw new InternalErrorException("Xen plugin \"vcp_privateGateway\" failed."); + } + } + } catch (Exception e) { String msg = "Unable to assign public IP address due to " + e.toString(); s_logger.warn(msg, e); diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index 6090ff06d7c..790c366cb0d 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -3120,6 +3120,7 @@ public class ApiResponseHelper implements ResponseGenerator { populateAccount(response, result.getAccountId()); populateDomain(response, result.getDomainId()); response.setState(result.getState().toString()); + response.setSourceNat(result.getSourceNat()); response.setObjectName("privategateway"); diff --git a/server/src/com/cloud/network/NetworkServiceImpl.java b/server/src/com/cloud/network/NetworkServiceImpl.java index ae0e4212efe..b2db06cad54 100755 --- a/server/src/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/com/cloud/network/NetworkServiceImpl.java @@ -3308,8 +3308,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { @Override @DB - public Network createPrivateNetwork(String networkName, String displayText, long physicalNetworkId, - String vlan, String startIp, String endIp, String gateway, String netmask, long networkOwnerId, Long vpcId) + public Network createPrivateNetwork(String networkName, String displayText, long physicalNetworkId, + String vlan, String startIp, String endIp, String gateway, String netmask, long networkOwnerId, Long vpcId, Boolean sourceNat) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException { Account owner = _accountMgr.getAccount(networkOwnerId); @@ -3377,7 +3377,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { Long nextMac = mac + 1; dc.setMacAddress(nextMac); - privateIp = new PrivateIpVO(startIp, privateNetwork.getId(), nextMac, vpcId); + privateIp = new PrivateIpVO(startIp, privateNetwork.getId(), nextMac, vpcId, sourceNat); _privateIpDao.persist(privateIp); _dcDao.update(dc.getId(), dc); diff --git a/server/src/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java b/server/src/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java index bdfac060798..ebf2d4257e3 100644 --- a/server/src/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java +++ b/server/src/com/cloud/network/router/VpcVirtualNetworkApplianceManagerImpl.java @@ -1178,8 +1178,8 @@ public class VpcVirtualNetworkApplianceManagerImpl extends VirtualNetworkApplian for (final PrivateIpAddress ipAddr : ipAddrList) { Network network = _networkModel.getNetwork(ipAddr.getNetworkId()); - IpAddressTO ip = new IpAddressTO(Account.ACCOUNT_ID_SYSTEM, ipAddr.getIpAddress(), add, false, - false, ipAddr.getVlanTag(), ipAddr.getGateway(), ipAddr.getNetmask(), ipAddr.getMacAddress(), + IpAddressTO ip = new IpAddressTO(Account.ACCOUNT_ID_SYSTEM, ipAddr.getIpAddress(), add, false, + ipAddr.getSourceNat(), ipAddr.getVlanTag(), ipAddr.getGateway(), ipAddr.getNetmask(), ipAddr.getMacAddress(), null, false); ip.setTrafficType(network.getTrafficType()); diff --git a/server/src/com/cloud/network/vpc/PrivateGatewayProfile.java b/server/src/com/cloud/network/vpc/PrivateGatewayProfile.java index 2595a6a0fa4..20947db0447 100644 --- a/server/src/com/cloud/network/vpc/PrivateGatewayProfile.java +++ b/server/src/com/cloud/network/vpc/PrivateGatewayProfile.java @@ -100,4 +100,9 @@ public class PrivateGatewayProfile implements PrivateGateway { public State getState() { return vpcGateway.getState(); } + + @Override + public boolean getSourceNat() { + return vpcGateway.getSourceNat(); + } } diff --git a/server/src/com/cloud/network/vpc/PrivateIpAddress.java b/server/src/com/cloud/network/vpc/PrivateIpAddress.java index 826bea22e25..2f3cf536e81 100644 --- a/server/src/com/cloud/network/vpc/PrivateIpAddress.java +++ b/server/src/com/cloud/network/vpc/PrivateIpAddress.java @@ -25,6 +25,7 @@ public class PrivateIpAddress implements PrivateIp{ String ipAddress; String macAddress; long networkId; + boolean sourceNat; /** * @param privateIp @@ -42,6 +43,7 @@ public class PrivateIpAddress implements PrivateIp{ this.netmask = netmask; this.macAddress = macAddress; this.networkId = privateIp.getNetworkId(); + this.sourceNat = privateIp.getSourceNat(); } @Override @@ -73,4 +75,9 @@ public class PrivateIpAddress implements PrivateIp{ public long getNetworkId() { return networkId; } + + @Override + public boolean getSourceNat() { + return sourceNat; + } } diff --git a/server/src/com/cloud/network/vpc/PrivateIpVO.java b/server/src/com/cloud/network/vpc/PrivateIpVO.java index e6616ae3899..952a0c2cf43 100644 --- a/server/src/com/cloud/network/vpc/PrivateIpVO.java +++ b/server/src/com/cloud/network/vpc/PrivateIpVO.java @@ -54,15 +54,19 @@ public class PrivateIpVO implements InternalIdentity { @Column(name="vpc_id") private Long vpcId; + + @Column(name="source_nat") + private boolean sourceNat; public PrivateIpVO() { - } + } - public PrivateIpVO(String ipAddress, long networkId, long macAddress, long vpcId) { + public PrivateIpVO(String ipAddress, long networkId, long macAddress, long vpcId, boolean sourceNat) { this.ipAddress = ipAddress; this.networkId = networkId; this.macAddress = macAddress; this.vpcId = vpcId; + this.sourceNat = sourceNat; } public void setTakenAt(Date takenDate) { @@ -92,4 +96,8 @@ public class PrivateIpVO implements InternalIdentity { public Long getVpcId() { return vpcId; } + public boolean getSourceNat() { + return sourceNat; + } + } diff --git a/server/src/com/cloud/network/vpc/VpcGatewayVO.java b/server/src/com/cloud/network/vpc/VpcGatewayVO.java index 718e4df82df..e8dcb46b211 100644 --- a/server/src/com/cloud/network/vpc/VpcGatewayVO.java +++ b/server/src/com/cloud/network/vpc/VpcGatewayVO.java @@ -29,7 +29,6 @@ import javax.persistence.Id; import javax.persistence.Table; import com.cloud.utils.db.GenericDao; -import org.apache.cloudstack.api.InternalIdentity; @Entity @@ -84,7 +83,10 @@ public class VpcGatewayVO implements VpcGateway { @Column(name="state") @Enumerated(value=EnumType.STRING) State state; - + + @Column(name="source_nat") + boolean sourceNat; + protected VpcGatewayVO(){ this.uuid = UUID.randomUUID().toString(); } @@ -101,9 +103,10 @@ public class VpcGatewayVO implements VpcGateway { * @param accountId TODO * @param domainId TODO * @param account_id + * @param sourceNat */ public VpcGatewayVO(String ip4Address, Type type, Long vpcId, long zoneId, Long networkId, String vlanTag, - String gateway, String netmask, long accountId, long domainId) { + String gateway, String netmask, long accountId, long domainId, boolean sourceNat) { this.ip4Address = ip4Address; this.type = type; this.vpcId = vpcId; @@ -116,6 +119,7 @@ public class VpcGatewayVO implements VpcGateway { this.accountId = accountId; this.domainId = domainId; this.state = State.Creating; + this.sourceNat = sourceNat; } @Override @@ -193,4 +197,10 @@ public class VpcGatewayVO implements VpcGateway { public void setState(State state) { this.state = state; } + + @Override + public boolean getSourceNat() { + return this.sourceNat; + } + } diff --git a/server/src/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/com/cloud/network/vpc/VpcManagerImpl.java index bc7bb0c75f2..a7f06e988dd 100644 --- a/server/src/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/com/cloud/network/vpc/VpcManagerImpl.java @@ -1285,8 +1285,8 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @Override @DB @ActionEvent(eventType = EventTypes.EVENT_PRIVATE_GATEWAY_CREATE, eventDescription = "creating vpc private gateway", create=true) - public PrivateGateway createVpcPrivateGateway(long vpcId, Long physicalNetworkId, String vlan, String ipAddress, - String gateway, String netmask, long gatewayOwnerId) throws ResourceAllocationException, + public PrivateGateway createVpcPrivateGateway(long vpcId, Long physicalNetworkId, String vlan, String ipAddress, + String gateway, String netmask, long gatewayOwnerId, Boolean isSourceNat) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException { //Validate parameters @@ -1312,11 +1312,11 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis //1) create private network String networkName = "vpc-" + vpc.getName() + "-privateNetwork"; Network privateNtwk = _ntwkSvc.createPrivateNetwork(networkName, networkName, physicalNetworkId, - vlan, ipAddress, null, gateway, netmask, gatewayOwnerId, vpcId); + vlan, ipAddress, null, gateway, netmask, gatewayOwnerId, vpcId, isSourceNat); //2) create gateway entry VpcGatewayVO gatewayVO = new VpcGatewayVO(ipAddress, VpcGateway.Type.Private, vpcId, privateNtwk.getDataCenterId(), - privateNtwk.getId(), vlan, gateway, netmask, vpc.getAccountId(), vpc.getDomainId()); + privateNtwk.getId(), vlan, gateway, netmask, vpc.getAccountId(), vpc.getDomainId(), isSourceNat); _vpcGatewayDao.persist(gatewayVO); s_logger.debug("Created vpc gateway entry " + gatewayVO); diff --git a/server/test/com/cloud/network/MockNetworkManagerImpl.java b/server/test/com/cloud/network/MockNetworkManagerImpl.java index e9987bd1b66..45562c6ea31 100755 --- a/server/test/com/cloud/network/MockNetworkManagerImpl.java +++ b/server/test/com/cloud/network/MockNetworkManagerImpl.java @@ -43,16 +43,8 @@ import com.cloud.user.Account; import com.cloud.user.User; import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; -import com.cloud.vm.Nic; -import com.cloud.vm.NicProfile; -import com.cloud.vm.NicSecondaryIp; -import com.cloud.vm.NicVO; -import com.cloud.vm.ReservationContext; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.VirtualMachine; +import com.cloud.vm.*; import com.cloud.vm.VirtualMachine.Type; -import com.cloud.vm.VirtualMachineProfile; -import com.cloud.vm.VirtualMachineProfileImpl; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.api.command.admin.usage.ListTrafficTypeImplementorsCmd; import org.apache.cloudstack.api.command.user.network.CreateNetworkCmd; @@ -604,7 +596,7 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage */ @Override public Network createPrivateNetwork(String networkName, String displayText, long physicalNetworkId, String vlan, - String startIp, String endIP, String gateway, String netmask, long networkOwnerId, Long vpcId) + String startIp, String endIP, String gateway, String netmask, long networkOwnerId, Long vpcId, Boolean sourceNat) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException { // TODO Auto-generated method stub return null; diff --git a/server/test/com/cloud/vpc/MockNetworkManagerImpl.java b/server/test/com/cloud/vpc/MockNetworkManagerImpl.java index 9b18358e258..668935707ee 100644 --- a/server/test/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/test/com/cloud/vpc/MockNetworkManagerImpl.java @@ -47,16 +47,8 @@ import com.cloud.user.Account; import com.cloud.user.User; import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; -import com.cloud.vm.Nic; -import com.cloud.vm.NicProfile; -import com.cloud.vm.NicSecondaryIp; -import com.cloud.vm.NicVO; -import com.cloud.vm.ReservationContext; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.VirtualMachine; +import com.cloud.vm.*; import com.cloud.vm.VirtualMachine.Type; -import com.cloud.vm.VirtualMachineProfile; -import com.cloud.vm.VirtualMachineProfileImpl; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.api.command.admin.usage.ListTrafficTypeImplementorsCmd; import org.apache.cloudstack.api.command.user.network.CreateNetworkCmd; @@ -609,7 +601,7 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage */ @Override public Network createPrivateNetwork(String networkName, String displayText, long physicalNetworkId, String vlan, - String startIp, String endIP, String gateway, String netmask, long networkOwnerId, Long vpcId) + String startIp, String endIP, String gateway, String netmask, long networkOwnerId, Long vpcId, Boolean sourceNat) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException { // TODO Auto-generated method stub return null; diff --git a/server/test/com/cloud/vpc/MockVpcManagerImpl.java b/server/test/com/cloud/vpc/MockVpcManagerImpl.java index 0f269284127..baccbd045d2 100644 --- a/server/test/com/cloud/vpc/MockVpcManagerImpl.java +++ b/server/test/com/cloud/vpc/MockVpcManagerImpl.java @@ -164,7 +164,7 @@ public class MockVpcManagerImpl extends ManagerBase implements VpcManager { * @see com.cloud.network.vpc.VpcService#createVpcPrivateGateway(long, java.lang.Long, java.lang.String, java.lang.String, java.lang.String, java.lang.String, long) */ @Override - public PrivateGateway createVpcPrivateGateway(long vpcId, Long physicalNetworkId, String vlan, String ipAddress, String gateway, String netmask, long gatewayOwnerId) throws ResourceAllocationException, + public PrivateGateway createVpcPrivateGateway(long vpcId, Long physicalNetworkId, String vlan, String ipAddress, String gateway, String netmask, long gatewayOwnerId, Boolean isSourceNat) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException { // TODO Auto-generated method stub return null; diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index 69bf886642c..99b476d4581 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -1123,3 +1123,6 @@ ALTER TABLE `cloud`.`account_details` MODIFY value varchar(255); INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'midonet.apiserver.address', 'http://localhost:8081', 'Specify the address at which the Midonet API server can be contacted (if using Midonet)'); INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'midonet.providerrouter.id', 'd7c5e6a3-e2f4-426b-b728-b7ce6a0448e5', 'Specifies the UUID of the Midonet provider router (if using Midonet)'); + +alter table cloud.vpc_gateways add column source_nat boolean default false; +alter table cloud.private_ip_address add column source_nat boolean default false; From 85a1cc962cb39baafd0aeb3457eeb227e848aef9 Mon Sep 17 00:00:00 2001 From: Sanjay Tripathi Date: Tue, 23 Apr 2013 10:01:56 +0530 Subject: [PATCH 23/47] CLOUDSTACK-2087: Destroying the instance is not decrementing the primary storage usage [Resource Count table - No of volumes is not got decremented] Signed-off-by: Sateesh Chodapuneedi --- .../cloud/resourcelimit/ResourceLimitManagerImpl.java | 2 +- .../src/com/cloud/vm/VirtualMachineManagerImpl.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/server/src/com/cloud/resourcelimit/ResourceLimitManagerImpl.java b/server/src/com/cloud/resourcelimit/ResourceLimitManagerImpl.java index e8805ae8910..5bb770871ca 100755 --- a/server/src/com/cloud/resourcelimit/ResourceLimitManagerImpl.java +++ b/server/src/com/cloud/resourcelimit/ResourceLimitManagerImpl.java @@ -582,7 +582,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim } //Convert max storage size from GiB to bytes - if (resourceType == ResourceType.primary_storage || resourceType == ResourceType.secondary_storage) { + if ((resourceType == ResourceType.primary_storage || resourceType == ResourceType.secondary_storage) && max >= 0) { max = max * ResourceType.bytesToGiB; } diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java index b0d6378e9e5..afffbec436b 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -64,6 +64,7 @@ import com.cloud.alert.AlertManager; import com.cloud.cluster.ClusterManager; import com.cloud.configuration.Config; import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.Resource.ResourceType; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.DataCenter; @@ -131,6 +132,7 @@ import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.storage.snapshot.SnapshotManager; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.user.ResourceLimitService; import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDao; @@ -231,6 +233,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac protected VMSnapshotDao _vmSnapshotDao; @Inject protected VolumeDataFactory volFactory; + @Inject + protected ResourceLimitService _resourceLimitMgr; protected List _planners; public List getPlanners() { @@ -428,6 +432,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.debug("Cleaning up NICS"); _networkMgr.cleanupNics(profile); // Clean up volumes based on the vm's instance id + List rootVol = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT); this.volumeMgr.cleanupVolumes(vm.getId()); VirtualMachineGuru guru = getVmGuru(vm); @@ -462,6 +467,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.debug("Expunged " + vm); } + // Update Resource count + if (vm.getAccountId() != Account.ACCOUNT_ID_SYSTEM && !rootVol.isEmpty()) { + _resourceLimitMgr.decrementResourceCount(vm.getAccountId(), ResourceType.volume); + _resourceLimitMgr.decrementResourceCount(vm.getAccountId(), ResourceType.primary_storage, + new Long(rootVol.get(0).getSize())); + } return true; } From 6a182c874c714b1dae7f60267c73510c36578bae Mon Sep 17 00:00:00 2001 From: Sateesh Chodapuneedi Date: Thu, 2 May 2013 17:04:55 +0530 Subject: [PATCH 24/47] Fixing build breakage. --- .../cloud/hypervisor/vmware/resource/VmwareResource.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 28a54e1e411..030eff0148a 100755 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -1479,11 +1479,11 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa snatArgs += " -c "; snatArgs += "eth" + ethDeviceNum; - Pair result = SshHelper.sshExecute(routerIp, DEFAULT_DOMR_SSHPORT, "root", mgr.getSystemVMKeyFile(), null, + Pair result_gateway = SshHelper.sshExecute(routerIp, DEFAULT_DOMR_SSHPORT, "root", mgr.getSystemVMKeyFile(), null, "/opt/cloud/bin/vpc_privateGateway.sh " + args); - if (!result.first()) { - throw new InternalErrorException("Unable to assign public IP address"); + if (!result_gateway.first()) { + throw new InternalErrorException("Unable to configure source NAT for public IP address."); } } From 565997d9b3e89936b066c43f429a4a4bf234c126 Mon Sep 17 00:00:00 2001 From: Sateesh Chodapuneedi Date: Thu, 2 May 2013 17:04:55 +0530 Subject: [PATCH 25/47] Fixing build breakage. Signed-off-by: Sateesh Chodapuneedi --- .../cloud/hypervisor/vmware/resource/VmwareResource.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 28a54e1e411..030eff0148a 100755 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -1479,11 +1479,11 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa snatArgs += " -c "; snatArgs += "eth" + ethDeviceNum; - Pair result = SshHelper.sshExecute(routerIp, DEFAULT_DOMR_SSHPORT, "root", mgr.getSystemVMKeyFile(), null, + Pair result_gateway = SshHelper.sshExecute(routerIp, DEFAULT_DOMR_SSHPORT, "root", mgr.getSystemVMKeyFile(), null, "/opt/cloud/bin/vpc_privateGateway.sh " + args); - if (!result.first()) { - throw new InternalErrorException("Unable to assign public IP address"); + if (!result_gateway.first()) { + throw new InternalErrorException("Unable to configure source NAT for public IP address."); } } From 61f3f931dd9df5354a39cfac0d60fccd782a6aab Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Thu, 2 May 2013 17:37:01 +0530 Subject: [PATCH 26/47] CLOUDSTACK-2041:Granular global parameter- Zone settings --- ui/scripts/system.js | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 5c3f3597dfa..0b5b3bc9b91 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -5523,20 +5523,48 @@ // Granular settings for zone settings: { - title: 'label.menu.global.settings', + title: 'Settings', custom: cloudStack.uiCustom.granularSettings({ dataProvider: function(args) { - args.response.success({ - data: [ - { name: 'config.param.1', value: 1 }, - { name: 'config.param.2', value: 2 } - ] - }); + $.ajax({ + url:createURL('listConfigurations&zoneid=' + args.context.physicalResources[0].id), + data: { page: args.page, pageSize: pageSize, listAll: true }, + success:function(json){ + args.response.success({ + data:json.listconfigurationsresponse.configuration + + }); + + }, + + error:function(json){ + args.response.error(parseXMLHttpResponse(json)); + + } + }); + }, actions: { edit: function(args) { // call updateZoneLevelParamter - args.response.success(); + var data = { + name: args.data.jsonObj.name, + value: args.data.value + }; + + $.ajax({ + url:createURL('updateConfiguration&zoneid=' + args.context.physicalResources[0].id), + data:data, + success:function(json){ + var item = json.updateconfigurationresponse.configuration; + args.response.success({data:item}); + }, + + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + + }); } } }) From d91b0f2f6d5359f168ea5b4b940afaa682049e3d Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Thu, 2 May 2013 18:23:35 +0530 Subject: [PATCH 27/47] CLOUDSTACK-2041:Granular global parameter- Cluster settings --- ui/scripts/system.js | 46 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 0b5b3bc9b91..8a28738e9be 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -9149,20 +9149,50 @@ // Granular settings for cluster settings: { - title: 'label.menu.global.settings', + title: 'Settings', custom: cloudStack.uiCustom.granularSettings({ dataProvider: function(args) { - args.response.success({ - data: [ - { name: 'config.param.1', value: 1 }, - { name: 'config.param.2', value: 2 } - ] - }); + $.ajax({ + url:createURL('listConfigurations&clusterid=' + args.context.clusters[0].id), + data: { page: args.page, pageSize: pageSize, listAll: true }, + success:function(json){ + args.response.success({ + data:json.listconfigurationsresponse.configuration + + }); + + }, + + error:function(json){ + args.response.error(parseXMLHttpResponse(json)); + + } + }); + }, actions: { edit: function(args) { // call updateClusterLevelParameters - args.response.success(); + + var data = { + name: args.data.jsonObj.name, + value: args.data.value + }; + + $.ajax({ + url:createURL('updateConfiguration&clusterid=' + args.context.clusters[0].id), + data:data, + success:function(json){ + var item = json.updateconfigurationresponse.configuration; + args.response.success({data:item}); + }, + + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + + }); + } } }) From 22a5c6d4b3fa3ba5ec883b930785365fbcce97cd Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Thu, 2 May 2013 20:06:20 +0530 Subject: [PATCH 28/47] CLOUDSTACK-2041:Granular global parameter- Account settings --- ui/scripts/accounts.js | 45 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/ui/scripts/accounts.js b/ui/scripts/accounts.js index 06fc07a72aa..bad8435e27e 100644 --- a/ui/scripts/accounts.js +++ b/ui/scripts/accounts.js @@ -900,20 +900,49 @@ // Granular settings for account settings: { - title: 'label.menu.global.settings', + title: 'Settings', custom: cloudStack.uiCustom.granularSettings({ dataProvider: function(args) { - args.response.success({ - data: [ - { name: 'config.param.1', value: 1 }, - { name: 'config.param.2', value: 2 } - ] - }); + $.ajax({ + url:createURL('listConfigurations&accountid=' + args.context.accounts[0].id), + data: { page: args.page, pageSize: pageSize, listAll: true }, + success:function(json){ + args.response.success({ + data:json.listconfigurationsresponse.configuration + + }); + + }, + + error:function(json){ + args.response.error(parseXMLHttpResponse(json)); + + } + }); + }, actions: { edit: function(args) { // call updateAccountLevelParameters - args.response.success(); + var data = { + name: args.data.jsonObj.name, + value: args.data.value + }; + + $.ajax({ + url:createURL('updateConfiguration&accountid=' + args.context.accounts[0].id), + data:data, + success:function(json){ + var item = json.updateconfigurationresponse.configuration; + args.response.success({data:item}); + }, + + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + + }); + } } }) From 4933533893982af12603c68c6285eafa3ce65420 Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Thu, 2 May 2013 20:11:17 +0530 Subject: [PATCH 29/47] CLOUDSTACK-2041:Granular global parameter- secondary storage settings not needed --- ui/scripts/system.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 8a28738e9be..b6e3bd16140 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -11032,10 +11032,10 @@ } }); } - }, - - // Granular settings for storage pool - settings: { + } + + // Granular settings for storage pool for secondary storage is not required + /* settings: { title: 'label.menu.global.settings', custom: cloudStack.uiCustom.granularSettings({ dataProvider: function(args) { @@ -11053,7 +11053,7 @@ } } }) - } + } */ } } } From ba9feabe8c5d5e4bc4634597f62caba520bbd1f6 Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Thu, 2 May 2013 21:02:51 +0530 Subject: [PATCH 30/47] Widget change to incorporate VM state while adding a load balancer rule --- ui/scripts/ui/widgets/multiEdit.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/scripts/ui/widgets/multiEdit.js b/ui/scripts/ui/widgets/multiEdit.js index 27b14d16e61..59e85bf8337 100755 --- a/ui/scripts/ui/widgets/multiEdit.js +++ b/ui/scripts/ui/widgets/multiEdit.js @@ -653,6 +653,11 @@ }); }); + var itemState=multiRule._itemState ? item[multiRule._itemState] :item.state; + var $itemState = $('').html(_s(itemState)); + $tr.append($('').addClass('state').appendTo($tr).append("VM State - ").append($itemState)); + + if (itemActions) { var $itemActions = $('').addClass('actions item-actions'); From 1206fd6b4e9584aaef3fa26f53201d9dbd41bd4d Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Thu, 2 May 2013 22:25:47 +0530 Subject: [PATCH 31/47] Widget change to incorporate VM state while adding a load balancer rule --- ui/scripts/ui/widgets/multiEdit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/scripts/ui/widgets/multiEdit.js b/ui/scripts/ui/widgets/multiEdit.js index 59e85bf8337..a1272fda31c 100755 --- a/ui/scripts/ui/widgets/multiEdit.js +++ b/ui/scripts/ui/widgets/multiEdit.js @@ -655,7 +655,7 @@ var itemState=multiRule._itemState ? item[multiRule._itemState] :item.state; var $itemState = $('').html(_s(itemState)); - $tr.append($('').addClass('state').appendTo($tr).append("VM State - ").append($itemState)); + $tr.append($('').addClass('state').appendTo($tr).append("Application State - ").append($itemState)); if (itemActions) { From f8504c096054d638fb5ed1e21e55243653c4649a Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Thu, 2 May 2013 13:10:28 -0400 Subject: [PATCH 32/47] CLOUDSTACK-2303: fix upgrade failed from 2.2.14 to 4.1.0 for systemvm changes The version of systemvm of 3.* and 4.0.* is systemvm-kvm-3.0.0.(from http://cloudstack.apache.org/docs/en-US/Apache_CloudStack/4.0.2/pdf/Release_Notes/Apache_CloudStack-4.0.2-Release_Notes-en-US.pdf) However, on cloudstack 4.1.0, the systemvm version is 4.1.0. (from http://dissociatedpress.net/uploads/Apache_CloudStack-4.1.0-Release_Notes-en-US.pdf) so the upgrade processing from 2.2.14 to 4.1.0 will abort at updateSystemVms in Upgrade2214to30.java. Signed-off-by: Chip Childers --- .../cloud/upgrade/dao/Upgrade2214to30.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) mode change 100755 => 100644 server/src/com/cloud/upgrade/dao/Upgrade2214to30.java diff --git a/server/src/com/cloud/upgrade/dao/Upgrade2214to30.java b/server/src/com/cloud/upgrade/dao/Upgrade2214to30.java old mode 100755 new mode 100644 index c0f827e655e..817231fa900 --- a/server/src/com/cloud/upgrade/dao/Upgrade2214to30.java +++ b/server/src/com/cloud/upgrade/dao/Upgrade2214to30.java @@ -629,8 +629,8 @@ public class Upgrade2214to30 extends Upgrade30xBase implements DbUpgrade { s_logger.debug("Updating XenSever System Vms"); //XenServer try { - //Get 3.0.0 xenserer system Vm template Id - pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name = 'systemvm-xenserver-3.0.0' and removed is null"); + //Get 3.0.0 or later xenserer system Vm template Id + pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name like 'systemvm-xenserver-%' and removed is null"); rs = pstmt.executeQuery(); if(rs.next()){ long templateId = rs.getLong(1); @@ -648,9 +648,9 @@ public class Upgrade2214to30 extends Upgrade30xBase implements DbUpgrade { pstmt.close(); } else { if (xenserver){ - throw new CloudRuntimeException("3.0.0 XenServer SystemVm template not found. Cannot upgrade system Vms"); + throw new CloudRuntimeException("3.0.0 or later XenServer SystemVm template not found. Cannot upgrade system Vms"); } else { - s_logger.warn("3.0.0 XenServer SystemVm template not found. XenServer hypervisor is not used, so not failing upgrade"); + s_logger.warn("3.0.0 or later XenServer SystemVm template not found. XenServer hypervisor is not used, so not failing upgrade"); } } } catch (SQLException e) { @@ -660,8 +660,8 @@ public class Upgrade2214to30 extends Upgrade30xBase implements DbUpgrade { //KVM s_logger.debug("Updating KVM System Vms"); try { - //Get 3.0.0 KVM system Vm template Id - pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name = 'systemvm-kvm-3.0.0' and removed is null"); + //Get 3.0.0 or later KVM system Vm template Id + pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name like 'systemvm-kvm-%' and removed is null"); rs = pstmt.executeQuery(); if(rs.next()){ long templateId = rs.getLong(1); @@ -679,9 +679,9 @@ public class Upgrade2214to30 extends Upgrade30xBase implements DbUpgrade { pstmt.close(); } else { if (kvm){ - throw new CloudRuntimeException("3.0.0 KVM SystemVm template not found. Cannot upgrade system Vms"); + throw new CloudRuntimeException("3.0.0 or later KVM SystemVm template not found. Cannot upgrade system Vms"); } else { - s_logger.warn("3.0.0 KVM SystemVm template not found. KVM hypervisor is not used, so not failing upgrade"); + s_logger.warn("3.0.0 or later KVM SystemVm template not found. KVM hypervisor is not used, so not failing upgrade"); } } } catch (SQLException e) { @@ -691,8 +691,8 @@ public class Upgrade2214to30 extends Upgrade30xBase implements DbUpgrade { //VMware s_logger.debug("Updating VMware System Vms"); try { - //Get 3.0.0 VMware system Vm template Id - pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name = 'systemvm-vmware-3.0.0' and removed is null"); + //Get 3.0.0 or later VMware system Vm template Id + pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name like 'systemvm-vmware-%' and removed is null"); rs = pstmt.executeQuery(); if(rs.next()){ long templateId = rs.getLong(1); @@ -710,9 +710,9 @@ public class Upgrade2214to30 extends Upgrade30xBase implements DbUpgrade { pstmt.close(); } else { if (VMware){ - throw new CloudRuntimeException("3.0.0 VMware SystemVm template not found. Cannot upgrade system Vms"); + throw new CloudRuntimeException("3.0.0 or later VMware SystemVm template not found. Cannot upgrade system Vms"); } else { - s_logger.warn("3.0.0 VMware SystemVm template not found. VMware hypervisor is not used, so not failing upgrade"); + s_logger.warn("3.0.0 or later VMware SystemVm template not found. VMware hypervisor is not used, so not failing upgrade"); } } } catch (SQLException e) { From 2510bf03f6d6b755fc82765c0363c2df43e4e401 Mon Sep 17 00:00:00 2001 From: Sheng Yang Date: Thu, 2 May 2013 19:58:39 -0700 Subject: [PATCH 33/47] CLOUDSTACK-2044: Use dnsmasq.conf.tmpl to generate dnsmasq.conf We add something like dhcp-range_ip4/ip6 in the template for implementing different setups. --- .../debian/config/etc/{dnsmasq.conf => dnsmasq.conf.tmpl} | 0 patches/systemvm/debian/config/etc/init.d/cloud-early-config | 3 +++ 2 files changed, 3 insertions(+) rename patches/systemvm/debian/config/etc/{dnsmasq.conf => dnsmasq.conf.tmpl} (100%) diff --git a/patches/systemvm/debian/config/etc/dnsmasq.conf b/patches/systemvm/debian/config/etc/dnsmasq.conf.tmpl similarity index 100% rename from patches/systemvm/debian/config/etc/dnsmasq.conf rename to patches/systemvm/debian/config/etc/dnsmasq.conf.tmpl diff --git a/patches/systemvm/debian/config/etc/init.d/cloud-early-config b/patches/systemvm/debian/config/etc/init.d/cloud-early-config index 6ffd648faeb..ed3894f61cb 100755 --- a/patches/systemvm/debian/config/etc/init.d/cloud-early-config +++ b/patches/systemvm/debian/config/etc/init.d/cloud-early-config @@ -442,6 +442,9 @@ setup_dnsmasq() { [ -z $DHCP_RANGE ] && [ $ETH0_IP ] && DHCP_RANGE=$ETH0_IP [ $ETH0_IP6 ] && DHCP_RANGE_IP6=$ETH0_IP6 [ -z $DOMAIN ] && DOMAIN="cloudnine.internal" + + #get the template + cp /etc/dnsmasq.conf.tmpl /etc/dnsmasq.conf if [ -n "$DOMAIN" ] then From d7cab21e9ae9df3f10dac502fd8f14f4deb3a348 Mon Sep 17 00:00:00 2001 From: Mice Xia Date: Fri, 3 May 2013 12:18:47 +0800 Subject: [PATCH 34/47] 1) rename RevertToSnapshotCmd->RevertToVMSnapshotCmd 2) add marvin test for vm snapshot --- ...hotCmd.java => RevertToVMSnapshotCmd.java} | 8 +- client/tomcatconf/commands.properties.in | 2 +- .../cloud/server/ManagementServerImpl.java | 4 +- test/integration/smoke/test_vm_snapshots.py | 308 ++++++++++++++++++ tools/marvin/marvin/integration/lib/base.py | 37 +++ ui/scripts/vm_snapshots.js | 2 +- 6 files changed, 353 insertions(+), 8 deletions(-) rename api/src/org/apache/cloudstack/api/command/user/vmsnapshot/{RevertToSnapshotCmd.java => RevertToVMSnapshotCmd.java} (91%) create mode 100644 test/integration/smoke/test_vm_snapshots.py diff --git a/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToSnapshotCmd.java b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToVMSnapshotCmd.java similarity index 91% rename from api/src/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToSnapshotCmd.java rename to api/src/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToVMSnapshotCmd.java index ea7bf60d6a3..f6d8b2c4a35 100644 --- a/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToSnapshotCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToVMSnapshotCmd.java @@ -37,11 +37,11 @@ import com.cloud.user.UserContext; import com.cloud.uservm.UserVm; import com.cloud.vm.snapshot.VMSnapshot; -@APICommand(name = "revertToSnapshot",description = "Revert VM from a vmsnapshot.", responseObject = UserVmResponse.class, since="4.2.0") -public class RevertToSnapshotCmd extends BaseAsyncCmd { +@APICommand(name = "revertToVMSnapshot",description = "Revert VM from a vmsnapshot.", responseObject = UserVmResponse.class, since="4.2.0") +public class RevertToVMSnapshotCmd extends BaseAsyncCmd { public static final Logger s_logger = Logger - .getLogger(RevertToSnapshotCmd.class.getName()); - private static final String s_name = "reverttosnapshotresponse"; + .getLogger(RevertToVMSnapshotCmd.class.getName()); + private static final String s_name = "reverttovmsnapshotresponse"; @Parameter(name = ApiConstants.VM_SNAPSHOT_ID, type = CommandType.UUID, required = true,entityType=VMSnapshotResponse.class,description = "The ID of the vm snapshot") private Long vmSnapShotId; diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index b49e1fbf5ff..7d950fe1696 100644 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -569,7 +569,7 @@ removeFromGlobalLoadBalancerRule=15 listVMSnapshot=15 createVMSnapshot=15 deleteVMSnapshot=15 -revertToSnapshot=15 +revertToVMSnapshot=15 #### Baremetal commands addBaremetalHost=1 diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index da01b83f79f..efd51e61c5f 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -256,7 +256,7 @@ import org.apache.cloudstack.api.command.user.vmgroup.UpdateVMGroupCmd; import org.apache.cloudstack.api.command.user.vmsnapshot.CreateVMSnapshotCmd; import org.apache.cloudstack.api.command.user.vmsnapshot.DeleteVMSnapshotCmd; import org.apache.cloudstack.api.command.user.vmsnapshot.ListVMSnapshotCmd; -import org.apache.cloudstack.api.command.user.vmsnapshot.RevertToSnapshotCmd; +import org.apache.cloudstack.api.command.user.vmsnapshot.RevertToVMSnapshotCmd; import org.apache.cloudstack.api.command.user.zone.ListZonesByCmd; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; @@ -2524,7 +2524,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(ListZonesByCmd.class); cmdList.add(ListVMSnapshotCmd.class); cmdList.add(CreateVMSnapshotCmd.class); - cmdList.add(RevertToSnapshotCmd.class); + cmdList.add(RevertToVMSnapshotCmd.class); cmdList.add(DeleteVMSnapshotCmd.class); cmdList.add(AddIpToVmNicCmd.class); cmdList.add(RemoveIpFromVmNicCmd.class); diff --git a/test/integration/smoke/test_vm_snapshots.py b/test/integration/smoke/test_vm_snapshots.py new file mode 100644 index 00000000000..353d499f7a1 --- /dev/null +++ b/test/integration/smoke/test_vm_snapshots.py @@ -0,0 +1,308 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 Local Modules +import marvin +from nose.plugins.attrib import attr +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * +from marvin.remoteSSHClient import remoteSSHClient + +class Services: + """Test Snapshots Services + """ + + def __init__(self): + self.services = { + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended for unique + # username + "password": "password", + }, + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 200, # in MHz + "memory": 256, # In MBs + }, + "server": { + "displayname": "TestVM", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "mgmt_server": { + "ipaddress": '1.2.2.152', + "username": "root", + "password": "password", + "port": 22, + }, + "templates": { + "displaytext": 'Template', + "name": 'Template', + "ostype": "CentOS 5.3 (64-bit)", + "templatefilter": 'self', + }, + "test_dir": "/tmp", + "random_data": "random.data", + "snapshot_name":"TestSnapshot", + "snapshot_displaytext":"Test", + "ostype": "CentOS 5.3 (64-bit)", + "sleep": 60, + "timeout": 10, + "mode": 'advanced', # Networking mode: Advanced, Basic + } + +class TestVmSnapshot(cloudstackTestCase): + @classmethod + def setUpClass(cls): + cls.api_client = super(TestVmSnapshot, cls).getClsTestClient().getApiClient() + cls.services = Services().services + # Get Zone, Domain and templates + cls.domain = get_domain(cls.api_client, cls.services) + cls.zone = get_zone(cls.api_client, cls.services) + + template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + cls.services["domainid"] = cls.domain.id + cls.services["server"]["zoneid"] = cls.zone.id + cls.services["templates"]["ostypeid"] = template.ostypeid + cls.services["zoneid"] = cls.zone.id + + # Create VMs, NAT Rules etc + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=cls.domain.id + ) + + cls.services["account"] = cls.account.name + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + cls.virtual_machine = VirtualMachine.create( + cls.api_client, + cls.services["server"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + mode=cls.services["mode"] + ) + cls.random_data_0 = random_gen(100) + cls._cleanup = [ + cls.service_offering, + cls.account, + ] + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created instance, volumes and snapshots + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags=["advanced", "advancedns", "smoke"]) + def test_01_create_vm_snapshots(self): + + try: + # Login to VM and write data to file system + ssh_client = self.virtual_machine.get_ssh_client() + + cmds = [ + "echo %s > %s/%s" % (self.random_data_0, self.services["test_dir"], self.services["random_data"]), + "cat %s/%s" % (self.services["test_dir"], self.services["random_data"]) + ] + + for c in cmds: + self.debug(c) + result = ssh_client.execute(c) + self.debug(result) + + except Exception: + self.fail("SSH failed for Virtual machine: %s" % + self.virtual_machine.ipaddress) + self.assertEqual( + self.random_data_0, + result[0], + "Check the random data has be write into temp file!" + ) + + time.sleep(self.services["sleep"]) + + vm_snapshot = VmSnapshot.create( + self.apiclient, + self.virtual_machine.id, + "false", + self.services["snapshot_name"], + self.services["snapshot_displaytext"] + ) + self.assertEqual( + vm_snapshot.state, + "Ready", + "Check the snapshot of vm is ready!" + ) + return + + @attr(tags=["advanced", "advancedns", "smoke"]) + def test_02_revert_vm_snapshots(self): + try: + ssh_client = self.virtual_machine.get_ssh_client() + + cmds = [ + "rm -rf %s/%s" % (self.services["test_dir"], self.services["random_data"]), + "ls %s/%s" % (self.services["test_dir"], self.services["random_data"]) + ] + + for c in cmds: + self.debug(c) + result = ssh_client.execute(c) + self.debug(result) + + except Exception: + self.fail("SSH failed for Virtual machine: %s" % + self.virtual_machine.ipaddress) + + if str(result[0]).index("No such file or directory") == -1: + self.fail("Check the random data has be delete from temp file!") + + time.sleep(self.services["sleep"]) + + list_snapshot_response = VmSnapshot.list(self.apiclient,vmid=self.virtual_machine.id,listall=True) + + self.assertEqual( + isinstance(list_snapshot_response, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + list_snapshot_response, + None, + "Check if snapshot exists in ListSnapshot" + ) + + self.assertEqual( + list_snapshot_response[0].state, + "Ready", + "Check the snapshot of vm is ready!" + ) + + VmSnapshot.revertToSnapshot(self.apiclient,list_snapshot_response[0].id) + + list_vm_response = list_virtual_machines( + self.apiclient, + id=self.virtual_machine.id + ) + + self.assertEqual( + list_vm_response[0].state, + "Stopped", + "Check the state of vm is Stopped!" + ) + + cmd = startVirtualMachine.startVirtualMachineCmd() + cmd.id = list_vm_response[0].id + self.apiclient.startVirtualMachine(cmd) + + time.sleep(self.services["sleep"]) + + try: + ssh_client = self.virtual_machine.get_ssh_client(reconnect=True) + + cmds = [ + "cat %s/%s" % (self.services["test_dir"], self.services["random_data"]) + ] + + for c in cmds: + self.debug(c) + result = ssh_client.execute(c) + self.debug(result) + + except Exception: + self.fail("SSH failed for Virtual machine: %s" % + self.virtual_machine.ipaddress) + + self.assertEqual( + self.random_data_0, + result[0], + "Check the random data is equal with the ramdom file!" + ) + @attr(tags=["advanced", "advancedns", "smoke"]) + def test_03_delete_vm_snapshots(self): + + list_snapshot_response = VmSnapshot.list(self.apiclient,vmid=self.virtual_machine.id,listall=True) + + self.assertEqual( + isinstance(list_snapshot_response, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + list_snapshot_response, + None, + "Check if snapshot exists in ListSnapshot" + ) + """ + cmd = deleteVMSnapshot.deleteVMSnapshotCmd() + cmd.vmsnapshotid = list_snapshot_response[0].id + self.apiclient.deleteVMSnapshot(cmd) + """ + VmSnapshot.deleteVMSnapshot(self.apiclient,list_snapshot_response[0].id) + + time.sleep(self.services["sleep"]*3) + + list_snapshot_response = VmSnapshot.list(self.apiclient,vmid=self.virtual_machine.id,listall=True) + + self.assertEqual( + list_snapshot_response, + None, + "Check list vm snapshot has be deleted" + ) diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py index 1d86c6c1599..bebf2b50bcf 100755 --- a/tools/marvin/marvin/integration/lib/base.py +++ b/tools/marvin/marvin/integration/lib/base.py @@ -3045,3 +3045,40 @@ class ASA1000V: cmd = listCiscoAsa1000vResources.listCiscoAsa1000vResourcesCmd() [setattr(cmd, k, v) for k, v in kwargs.items()] return(apiclient.listCiscoAsa1000vResources(cmd)) + +class VmSnapshot: + """Manage VM Snapshot life cycle""" + def __init__(self, items): + self.__dict__.update(items) + @classmethod + def create(cls,apiclient,vmid,snapshotmemory="false",name=None,description=None): + cmd = createVMSnapshot.createVMSnapshotCmd() + cmd.virtualmachineid = vmid + + if snapshotmemory: + cmd.snapshotmemory = snapshotmemory + if name: + cmd.name = name + if description: + cmd.description = description + return VmSnapshot(apiclient.createVMSnapshot(cmd).__dict__) + + @classmethod + def list(cls, apiclient, **kwargs): + cmd = listVMSnapshot.listVMSnapshotCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listVMSnapshot(cmd)) + + @classmethod + def revertToSnapshot(cls, apiclient,vmsnapshotid): + cmd = revertToVMSnapshot.revertToVMSnapshotCmd() + cmd.vmsnapshotid = vmsnapshotid + + return apiclient.revertToVMSnapshot(cmd) + + @classmethod + def deleteVMSnapshot(cls,apiclient,vmsnapshotid): + cmd = deleteVMSnapshot.deleteVMSnapshotCmd() + cmd.vmsnapshotid = vmsnapshotid + + return apiclient.deleteVMSnapshot(cmd) diff --git a/ui/scripts/vm_snapshots.js b/ui/scripts/vm_snapshots.js index 0d6305bfbe4..190bf7521f3 100644 --- a/ui/scripts/vm_snapshots.js +++ b/ui/scripts/vm_snapshots.js @@ -170,7 +170,7 @@ }, action: function(args) { $.ajax({ - url: createURL("revertToSnapshot&vmsnapshotid=" + args.context.vmsnapshots[0].id), + url: createURL("revertToVMSnapshot&vmsnapshotid=" + args.context.vmsnapshots[0].id), dataType: "json", async: true, success: function(json) { From 28bd1a1af7a15d18cb5fba835fdedade05b5d8c5 Mon Sep 17 00:00:00 2001 From: Sateesh Chodapuneedi Date: Fri, 3 May 2013 10:40:19 +0530 Subject: [PATCH 35/47] CLOUDSTACK-2316 Vmware DVSwitch Managed Object reference not being returned by vCenter Set correct property spec path name. Signed-off-by: Sateesh Chodapuneedi --- .../src/com/cloud/hypervisor/vmware/util/VmwareClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vmware-base/src/com/cloud/hypervisor/vmware/util/VmwareClient.java b/vmware-base/src/com/cloud/hypervisor/vmware/util/VmwareClient.java index e69a3d26c56..87c79096d60 100644 --- a/vmware-base/src/com/cloud/hypervisor/vmware/util/VmwareClient.java +++ b/vmware-base/src/com/cloud/hypervisor/vmware/util/VmwareClient.java @@ -528,7 +528,7 @@ public class VmwareClient { PropertySpec pSpec = new PropertySpec(); pSpec.setType(type); pSpec.setAll(false); - pSpec.getPathSet().add(name); + pSpec.getPathSet().add("name"); ObjectSpec oSpec = new ObjectSpec(); oSpec.setObj(root); From 8832a529fc8fec96f00e201b80fe1f09b587da15 Mon Sep 17 00:00:00 2001 From: Sheng Yang Date: Thu, 2 May 2013 22:12:31 -0700 Subject: [PATCH 36/47] Fix rat exclude dnsmasq.conf.tmpl --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9526d92c112..57073e3b19a 100644 --- a/pom.xml +++ b/pom.xml @@ -399,7 +399,7 @@ patches/systemvm/debian/config/etc/apache2/sites-available/default patches/systemvm/debian/config/etc/apache2/sites-available/default-ssl patches/systemvm/debian/config/etc/apache2/vhostexample.conf - patches/systemvm/debian/config/etc/dnsmasq.conf + patches/systemvm/debian/config/etc/dnsmasq.conf.tmpl patches/systemvm/debian/config/etc/vpcdnsmasq.conf patches/systemvm/debian/config/etc/ssh/sshd_config patches/systemvm/debian/config/etc/rsyslog.conf From 4a40f3f6dade062c7996611d4b8918d3fbd59d73 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Thu, 2 May 2013 15:42:53 +0530 Subject: [PATCH 37/47] CLOUDSTACK-741: Granular Global Parameters fixing network.throttling.rate to work per zone in vmware Signed-off-by: Abhinandan Prateek --- server/src/com/cloud/network/NetworkModelImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/com/cloud/network/NetworkModelImpl.java b/server/src/com/cloud/network/NetworkModelImpl.java index 7b712ea9781..fdf722c33de 100755 --- a/server/src/com/cloud/network/NetworkModelImpl.java +++ b/server/src/com/cloud/network/NetworkModelImpl.java @@ -924,9 +924,9 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel { } } if (isUserVmsDefaultNetwork || isDomRGuestOrPublicNetwork) { - return _configMgr.getServiceOfferingNetworkRate(vm.getServiceOfferingId(), vm.getDataCenterId()); + return _configMgr.getServiceOfferingNetworkRate(vm.getServiceOfferingId(), network.getDataCenterId()); } else { - return _configMgr.getNetworkOfferingNetworkRate(ntwkOff.getId(), vm.getDataCenterId()); + return _configMgr.getNetworkOfferingNetworkRate(ntwkOff.getId(), network.getDataCenterId()); } } From 324b4f680ca6632e1762079859ef696824ee0bef Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Fri, 3 May 2013 11:16:15 +0530 Subject: [PATCH 38/47] CLOUDSTACK-2041:Granular global parameter- Storage Pool granular settings --- ui/scripts/system.js | 46 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index b6e3bd16140..65889b3f012 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -10820,20 +10820,50 @@ // Granular settings for storage pool settings: { - title: 'label.menu.global.settings', + title: 'Settings', custom: cloudStack.uiCustom.granularSettings({ dataProvider: function(args) { - args.response.success({ - data: [ - { name: 'config.param.1', value: 1 }, - { name: 'config.param.2', value: 2 } - ] - }); + + $.ajax({ + url:createURL('listConfigurations&storageid=' + args.context.primarystorages[0].id), + data: { page: args.page, pageSize: pageSize, listAll: true }, + success:function(json){ + args.response.success({ + data:json.listconfigurationsresponse.configuration + + }); + + }, + + error:function(json){ + args.response.error(parseXMLHttpResponse(json)); + + } + }); + }, actions: { edit: function(args) { // call updateStorageLevelParameters - args.response.success(); + var data = { + name: args.data.jsonObj.name, + value: args.data.value + }; + + $.ajax({ + url:createURL('updateConfiguration&storageid=' + args.context.primarystorages[0].id), + data:data, + success:function(json){ + var item = json.updatestoragelevelparameterresponse.configuration; + args.response.success({data:item}); + }, + + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + + }); + } } }) From 3087bf7e5bd6985a09a8fa5eba35cd5208872b3e Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Fri, 3 May 2013 11:21:00 +0530 Subject: [PATCH 39/47] CLOUDSTACK-2041:Granular global parameter- Storage Pool granular settings --- ui/scripts/system.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 65889b3f012..3c4051cc481 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -10854,7 +10854,7 @@ url:createURL('updateConfiguration&storageid=' + args.context.primarystorages[0].id), data:data, success:function(json){ - var item = json.updatestoragelevelparameterresponse.configuration; + var item = json.updateconfigurationresponse.configuration; args.response.success({data:item}); }, From 0680fbdff3876723035ae86ab9b7c577a9452836 Mon Sep 17 00:00:00 2001 From: Murali Reddy Date: Fri, 3 May 2013 11:36:58 +0530 Subject: [PATCH 40/47] CLOUDSTACK-2193: associateIpAddress is failing in "basic zone" that is enabled for EIP/ELB restricting check that shared network should have services in orider to associate ip, to advaced zone shared networks only. On basic zone shared network associateIpAddress should work. --- .../com/cloud/network/NetworkServiceImpl.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/server/src/com/cloud/network/NetworkServiceImpl.java b/server/src/com/cloud/network/NetworkServiceImpl.java index b2db06cad54..d8f8d5dea2d 100755 --- a/server/src/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/com/cloud/network/NetworkServiceImpl.java @@ -426,17 +426,19 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { } // if shared network in the advanced zone, then check the caller against the network for 'AccessType.UseNetwork' - if (isSharedNetworkOfferingWithServices(network.getNetworkOfferingId()) && zone.getNetworkType() == NetworkType.Advanced) { - Account caller = UserContext.current().getCaller(); - long callerUserId = UserContext.current().getCallerUserId(); - _accountMgr.checkAccess(caller, AccessType.UseNetwork, false, network); - if (s_logger.isDebugEnabled()) { - s_logger.debug("Associate IP address called by the user " + callerUserId + " account " + ipOwner.getId()); + if (zone.getNetworkType() == NetworkType.Advanced) { + if (isSharedNetworkOfferingWithServices(network.getNetworkOfferingId())) { + Account caller = UserContext.current().getCaller(); + long callerUserId = UserContext.current().getCallerUserId(); + _accountMgr.checkAccess(caller, AccessType.UseNetwork, false, network); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Associate IP address called by the user " + callerUserId + " account " + ipOwner.getId()); + } + return _networkMgr.allocateIp(ipOwner, false, caller, callerUserId, zone); + } else { + throw new InvalidParameterValueException("Associate IP address can only be called on the shared networks in the advanced zone" + + " with Firewall/Source Nat/Static Nat/Port Forwarding/Load balancing services enabled"); } - return _networkMgr.allocateIp(ipOwner, false, caller, callerUserId, zone); - } else { - throw new InvalidParameterValueException("Associate IP address can only be called on the shared networks in the advanced zone" + - " with Firewall/Source Nat/Static Nat/Port Forwarding/Load balancing services enabled"); } } } From 3ae481973281e4ba73f6791f067f7b6ffe9efc3b Mon Sep 17 00:00:00 2001 From: Murali Reddy Date: Fri, 3 May 2013 12:36:02 +0530 Subject: [PATCH 41/47] fixes bug which was storing NetScale site public IP as site private IP --- .../cloud/network/ExternalLoadBalancerDeviceManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java b/server/src/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java index b4662d13df3..686f5bc2a05 100644 --- a/server/src/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java +++ b/server/src/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java @@ -270,7 +270,7 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase lbDeviceVO = new ExternalLoadBalancerDeviceVO(host.getId(), pNetwork.getId(), ntwkDevice.getNetworkServiceProvder(), deviceName, capacity, dedicatedUse, gslbProvider); if (gslbProvider) { - lbDeviceVO.setGslbSitePrivateIP(gslbSitePublicIp); + lbDeviceVO.setGslbSitePublicIP(gslbSitePublicIp); lbDeviceVO.setGslbSitePrivateIP(gslbSitePrivateIp); } _externalLoadBalancerDeviceDao.persist(lbDeviceVO); From a3a5862301b5b3d79abcf8dfef66283728954d55 Mon Sep 17 00:00:00 2001 From: Murali Reddy Date: Fri, 3 May 2013 15:37:25 +0530 Subject: [PATCH 42/47] CLOUDSTACK-2317: NPE while cloudstack trying to get system ip and enable static nat for the vm VM splitting enableStaticNat() method in to a service and manager layer method. So as to ensure action event annotation is present only on service layer method. --- api/src/com/cloud/network/rules/RulesService.java | 2 +- .../com/cloud/network/rules/RulesManagerImpl.java | 13 +++++++++++-- .../com/cloud/network/MockRulesManagerImpl.java | 3 +-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/api/src/com/cloud/network/rules/RulesService.java b/api/src/com/cloud/network/rules/RulesService.java index d47b38f9d43..45abd84a9ec 100644 --- a/api/src/com/cloud/network/rules/RulesService.java +++ b/api/src/com/cloud/network/rules/RulesService.java @@ -67,7 +67,7 @@ public interface RulesService { boolean applyPortForwardingRules(long ipAdddressId, Account caller) throws ResourceUnavailableException; - boolean enableStaticNat(long ipAddressId, long vmId, long networkId, boolean isSystemVm, String vmGuestIp) throws NetworkRuleConflictException, ResourceUnavailableException; + boolean enableStaticNat(long ipAddressId, long vmId, long networkId, String vmGuestIp) throws NetworkRuleConflictException, ResourceUnavailableException; PortForwardingRule getPortForwardigRule(long ruleId); diff --git a/server/src/com/cloud/network/rules/RulesManagerImpl.java b/server/src/com/cloud/network/rules/RulesManagerImpl.java index 8636d8503a3..23556354e3a 100755 --- a/server/src/com/cloud/network/rules/RulesManagerImpl.java +++ b/server/src/com/cloud/network/rules/RulesManagerImpl.java @@ -80,12 +80,14 @@ import com.cloud.utils.net.Ip; import com.cloud.vm.Nic; import com.cloud.vm.NicSecondaryIp; import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.Type; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.NicSecondaryIpDao; import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; @Component @Local(value = { RulesManager.class, RulesService.class }) @@ -103,6 +105,8 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules @Inject UserVmDao _vmDao; @Inject + VMInstanceDao _vmInstanceDao; + @Inject AccountManager _accountMgr; @Inject NetworkManager _networkMgr; @@ -416,7 +420,12 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules @Override @ActionEvent(eventType = EventTypes.EVENT_ENABLE_STATIC_NAT, eventDescription = "enabling static nat") - public boolean enableStaticNat(long ipId, long vmId, long networkId, boolean isSystemVm, String vmGuestIp) + public boolean enableStaticNat(long ipId, long vmId, long networkId, String vmGuestIp) + throws NetworkRuleConflictException, ResourceUnavailableException { + return enableStaticNat(ipId, vmId, networkId, false, vmGuestIp); + } + + private boolean enableStaticNat(long ipId, long vmId, long networkId, boolean isSystemVm, String vmGuestIp) throws NetworkRuleConflictException, ResourceUnavailableException { UserContext ctx = UserContext.current(); Account caller = ctx.getCaller(); @@ -1370,7 +1379,7 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules throw new CloudRuntimeException("Ip address is not associated with any network"); } - UserVmVO vm = _vmDao.findById(sourceIp.getAssociatedWithVmId()); + VMInstanceVO vm = _vmInstanceDao.findById(sourceIp.getAssociatedWithVmId()); Network network = _networkModel.getNetwork(networkId); if (network == null) { CloudRuntimeException ex = new CloudRuntimeException("Unable to find an ip address to map to specified vm id"); diff --git a/server/test/com/cloud/network/MockRulesManagerImpl.java b/server/test/com/cloud/network/MockRulesManagerImpl.java index e5a6894d76d..200fd2c7462 100644 --- a/server/test/com/cloud/network/MockRulesManagerImpl.java +++ b/server/test/com/cloud/network/MockRulesManagerImpl.java @@ -76,8 +76,7 @@ public class MockRulesManagerImpl extends ManagerBase implements RulesManager, R @Override public boolean enableStaticNat(long ipAddressId, long vmId, long networkId, - boolean isSystemVm, String ipAddr) throws NetworkRuleConflictException, - ResourceUnavailableException { + String ipAddr) throws NetworkRuleConflictException, ResourceUnavailableException { // TODO Auto-generated method stub return false; } From 8b909668fb5adc6c5c92cb9f00e9931555668123 Mon Sep 17 00:00:00 2001 From: Murali Reddy Date: Fri, 3 May 2013 16:06:43 +0530 Subject: [PATCH 43/47] CLOUDSTACK-2320: On NetScaler RNAT rules are not getting created, blocking public access to the VM's in basic zone using EIP. Its required that both RNAT and INAT rules are required on the NetScaler to provide public connectivity to user VM's in both in-bound and out-bound directions. Currenely only INAT rule is added which permits inbound public traffic to VM. This fix adds RNAT rule aswell, which ensures the outbound public access from the user VM's --- .../network/resource/NetscalerResource.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/plugins/network-elements/netscaler/src/com/cloud/network/resource/NetscalerResource.java b/plugins/network-elements/netscaler/src/com/cloud/network/resource/NetscalerResource.java index 677bc785b2c..563cbd47389 100644 --- a/plugins/network-elements/netscaler/src/com/cloud/network/resource/NetscalerResource.java +++ b/plugins/network-elements/netscaler/src/com/cloud/network/resource/NetscalerResource.java @@ -1618,7 +1618,9 @@ public class NetscalerResource implements ServerResource { String srcIp = rule.getSrcIp(); String dstIP = rule.getDstIp(); String iNatRuleName = generateInatRuleName(srcIp, dstIP); + String rNatRuleName = generateRnatRuleName(srcIp, dstIP); inat iNatRule = null; + rnat rnatRule = null; if (!rule.revoked()) { try { @@ -1645,9 +1647,47 @@ public class NetscalerResource implements ServerResource { } s_logger.debug("Created Inat rule on the Netscaler device " + _ip + " to enable static NAT from " + srcIp + " to " + dstIP); } + try { + rnat[] rnatRules = rnat.get(_netscalerService); + if (rnatRules != null) { + for (rnat rantrule : rnatRules) { + if (rantrule.get_network().equalsIgnoreCase(rNatRuleName)) { + rnatRule = rantrule; + break; + } + } + } + } catch (nitro_exception e) { + throw e; + } + + if (rnatRule == null) { + rnatRule = new rnat(); + rnatRule.set_natip(srcIp); + rnatRule.set_network(dstIP); + rnatRule.set_netmask("255.255.255.255"); + try { + apiCallResult = rnat.update(_netscalerService, rnatRule); + } catch (nitro_exception e) { + if (e.getErrorCode() != NitroError.NS_RESOURCE_EXISTS) { + throw e; + } + } + s_logger.debug("Created Rnat rule on the Netscaler device " + _ip + " to enable revese static NAT from " + dstIP + " to " + srcIp); + } } else { try { inat.delete(_netscalerService, iNatRuleName); + rnat[] rnatRules = rnat.get(_netscalerService); + if (rnatRules != null) { + for (rnat rantrule : rnatRules) { + if (rantrule.get_network().equalsIgnoreCase(dstIP)) { + rnatRule = rantrule; + rnat.clear(_netscalerService, rnatRule); + break; + } + } + } } catch (nitro_exception e) { if (e.getErrorCode() != NitroError.NS_RESOURCE_NOT_EXISTS) { throw e; @@ -3090,6 +3130,10 @@ public class NetscalerResource implements ServerResource { return genObjectName("Cloud-Inat", srcIp); } + private String generateRnatRuleName(String srcIp, String dstIP) { + return genObjectName("Cloud-Rnat", srcIp); + } + private String generateNSVirtualServerName(String srcIp, long srcPort) { return genObjectName("Cloud-VirtualServer", srcIp, srcPort); } From 64fb826640c0f10e5a58ccdfd53d0ce3a4d97a6b Mon Sep 17 00:00:00 2001 From: Murali Reddy Date: Fri, 3 May 2013 16:35:28 +0530 Subject: [PATCH 44/47] fixing build break due to commit a3a5862301b5b3d79abcf8dfef66283728954d55 --- .../cloudstack/api/command/user/nat/EnableStaticNatCmd.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/org/apache/cloudstack/api/command/user/nat/EnableStaticNatCmd.java b/api/src/org/apache/cloudstack/api/command/user/nat/EnableStaticNatCmd.java index a0ec68ef5dd..902dbae00aa 100644 --- a/api/src/org/apache/cloudstack/api/command/user/nat/EnableStaticNatCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/nat/EnableStaticNatCmd.java @@ -120,7 +120,7 @@ public class EnableStaticNatCmd extends BaseCmd{ @Override public void execute() throws ResourceUnavailableException{ try { - boolean result = _rulesService.enableStaticNat(ipAddressId, virtualMachineId, getNetworkId(), false, getVmSecondaryIp()); + boolean result = _rulesService.enableStaticNat(ipAddressId, virtualMachineId, getNetworkId(), getVmSecondaryIp()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); From 38b4f84f1749f86ac058b25d86995fe54bc7fde6 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Fri, 3 May 2013 14:52:45 +0530 Subject: [PATCH 45/47] CLOUDSTACK-2146: system vm scaleup failed ;{ "scalevirtualmachineresponse" : {"errorcode":530,"cserrorcode":9999,"errortext":"Failed to scale vm"} } Only response generation for system vm scale up failed so fixed by changing the response object. Signed-off-by: Abhinandan Prateek --- api/src/com/cloud/vm/UserVmService.java | 2 +- .../api/command/user/vm/ScaleVMCmd.java | 10 +++--- .../api/command/test/ScaleVMCmdTest.java | 35 ++++--------------- .../src/com/cloud/vm/UserVmManagerImpl.java | 10 +++--- .../com/cloud/vm/MockUserVmManagerImpl.java | 4 +-- 5 files changed, 19 insertions(+), 42 deletions(-) diff --git a/api/src/com/cloud/vm/UserVmService.java b/api/src/com/cloud/vm/UserVmService.java index c8ccb67caf3..fa89521af0a 100755 --- a/api/src/com/cloud/vm/UserVmService.java +++ b/api/src/com/cloud/vm/UserVmService.java @@ -451,6 +451,6 @@ public interface UserVmService { UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException; - UserVm upgradeVirtualMachine(ScaleVMCmd scaleVMCmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException; + boolean upgradeVirtualMachine(ScaleVMCmd scaleVMCmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException; } diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java index 4fc65c37e58..4f2ac750ce5 100644 --- a/api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java @@ -22,11 +22,12 @@ import com.cloud.user.UserContext; import com.cloud.uservm.UserVm; import org.apache.cloudstack.api.*; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.log4j.Logger; -@APICommand(name = "scaleVirtualMachine", description="Scales the virtual machine to a new service offering.", responseObject=UserVmResponse.class) +@APICommand(name = "scaleVirtualMachine", description="Scales the virtual machine to a new service offering.", responseObject=SuccessResponse.class) public class ScaleVMCmd extends BaseCmd { public static final Logger s_logger = Logger.getLogger(ScaleVMCmd.class.getName()); private static final String s_name = "scalevirtualmachineresponse"; @@ -83,7 +84,7 @@ public class ScaleVMCmd extends BaseCmd { @Override public void execute(){ //UserContext.current().setEventDetails("Vm Id: "+getId()); - UserVm result = null; + boolean result; try { result = _userVmService.upgradeVirtualMachine(this); } catch (ResourceUnavailableException ex) { @@ -99,9 +100,8 @@ public class ScaleVMCmd extends BaseCmd { s_logger.warn("Exception: ", ex); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); } - if (result != null){ - UserVmResponse response = _responseGenerator.createUserVmResponse("virtualmachine", result).get(0); - response.setResponseName(getCommandName()); + if (result){ + SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to scale vm"); diff --git a/api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java b/api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java index 301fa02ca29..8a28290e04b 100644 --- a/api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java +++ b/api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java @@ -16,31 +16,20 @@ // under the License. package org.apache.cloudstack.api.command.test; -import com.cloud.user.Account; -import com.cloud.user.UserContext; import com.cloud.uservm.UserVm; import com.cloud.vm.UserVmService; import junit.framework.Assert; import junit.framework.TestCase; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.command.admin.region.AddRegionCmd; import org.apache.cloudstack.api.command.user.vm.ScaleVMCmd; -import org.apache.cloudstack.api.response.RegionResponse; -import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.region.Region; -import org.apache.cloudstack.region.RegionService; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Mockito; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - - public class ScaleVMCmdTest extends TestCase{ private ScaleVMCmd scaleVMCmd; @@ -57,12 +46,11 @@ public class ScaleVMCmdTest extends TestCase{ public Long getId() { return 2L; } + @Override + public String getCommandName() { + return "scalevirtualmachineresponse"; + } }; - - //Account account = new AccountVO("testaccount", 1L, "networkdomain", (short) 0, "uuid"); - //UserContext.registerContext(1, account, null, true); - - } @@ -71,11 +59,10 @@ public class ScaleVMCmdTest extends TestCase{ UserVmService userVmService = Mockito.mock(UserVmService.class); - UserVm uservm = Mockito.mock(UserVm.class); try { Mockito.when( userVmService.upgradeVirtualMachine(scaleVMCmd)) - .thenReturn(uservm); + .thenReturn(true); }catch (Exception e){ Assert.fail("Received exception when success expected " +e.getMessage()); } @@ -83,13 +70,6 @@ public class ScaleVMCmdTest extends TestCase{ scaleVMCmd._userVmService = userVmService; responseGenerator = Mockito.mock(ResponseGenerator.class); - UserVmResponse userVmResponse = Mockito.mock(UserVmResponse.class); - List responseList = new ArrayList(); - responseList.add(userVmResponse); - - Mockito.when(responseGenerator.createUserVmResponse("virtualmachine",uservm)) - .thenReturn(responseList); - scaleVMCmd._responseGenerator = responseGenerator; scaleVMCmd.execute(); @@ -101,10 +81,9 @@ public class ScaleVMCmdTest extends TestCase{ UserVmService userVmService = Mockito.mock(UserVmService.class); try { - UserVm uservm = Mockito.mock(UserVm.class); Mockito.when( userVmService.upgradeVirtualMachine(scaleVMCmd)) - .thenReturn(null); + .thenReturn(false); }catch (Exception e){ Assert.fail("Received exception when success expected " +e.getMessage()); } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 617994888bd..bc25bedde45 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -1050,7 +1050,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use @Override @ActionEvent(eventType = EventTypes.EVENT_VM_SCALE, eventDescription = "scaling Vm") - public UserVm + public boolean upgradeVirtualMachine(ScaleVMCmd cmd) throws InvalidParameterValueException { Long vmId = cmd.getId(); Long newServiceOfferingId = cmd.getServiceOfferingId(); @@ -1076,8 +1076,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } // Dynamically upgrade the running vms + boolean success = false; if(vmInstance.getState().equals(State.Running)){ - boolean success = false; int retry = _scaleRetry; while (retry-- != 0) { // It's != so that it can match -1. try{ @@ -1095,7 +1095,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use vmInstance = _vmInstanceDao.findById(vmId); vmInstance = _itMgr.reConfigureVm(vmInstance, oldServiceOffering, existingHostHasCapacity); success = true; - return _vmDao.findById(vmInstance.getId()); + return success; }catch(InsufficientCapacityException e ){ s_logger.warn("Received exception while scaling ",e); } catch (ResourceUnavailableException e) { @@ -1112,11 +1112,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } } } - if (!success) - return null; } - return _vmDao.findById(vmInstance.getId()); + return success; } diff --git a/server/test/com/cloud/vm/MockUserVmManagerImpl.java b/server/test/com/cloud/vm/MockUserVmManagerImpl.java index 22bbbe8d5df..8b0b1c797c0 100644 --- a/server/test/com/cloud/vm/MockUserVmManagerImpl.java +++ b/server/test/com/cloud/vm/MockUserVmManagerImpl.java @@ -407,8 +407,8 @@ public class MockUserVmManagerImpl extends ManagerBase implements UserVmManager, } @Override - public UserVm upgradeVirtualMachine(ScaleVMCmd scaleVMCmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException { - return null; //To change body of implemented methods use File | Settings | File Templates. + public boolean upgradeVirtualMachine(ScaleVMCmd scaleVMCmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException { + return false; //To change body of implemented methods use File | Settings | File Templates. } From c8e3fff6e43954bdd0c33aabcd0c17fa3799c4f3 Mon Sep 17 00:00:00 2001 From: Rajesh Battala Date: Wed, 17 Apr 2013 17:16:56 +0530 Subject: [PATCH 46/47] CLOUDSTACK-1849 Creation of LB Health Check policy fails if the value of Unhealthy Threshold parameter is 3 or more. Signed-off-by: Sateesh Chodapuneedi --- .../src/com/cloud/network/resource/NetscalerResource.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/network-elements/netscaler/src/com/cloud/network/resource/NetscalerResource.java b/plugins/network-elements/netscaler/src/com/cloud/network/resource/NetscalerResource.java index 563cbd47389..98e14618248 100644 --- a/plugins/network-elements/netscaler/src/com/cloud/network/resource/NetscalerResource.java +++ b/plugins/network-elements/netscaler/src/com/cloud/network/resource/NetscalerResource.java @@ -2297,6 +2297,7 @@ public class NetscalerResource implements ServerResource { } csMon.set_interval(hcp.getHealthcheckInterval()); + csMon.set_retries(Math.max(hcp.getHealthcheckThresshold(), hcp.getUnhealthThresshold()) + 1); csMon.set_resptimeout(hcp.getResponseTime()); csMon.set_failureretries(hcp.getUnhealthThresshold()); csMon.set_successretries(hcp.getHealthcheckThresshold()); From a3a5c13427d6871f87da291425314b18ef64fa2a Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Fri, 3 May 2013 16:01:44 +0100 Subject: [PATCH 47/47] CLOUDSTACK-2303: upgrade failed from 2.2.14 to 4.1.0 - part2 Signed-off-by: Chip Childers --- server/src/com/cloud/upgrade/dao/Upgrade2214to30.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/com/cloud/upgrade/dao/Upgrade2214to30.java b/server/src/com/cloud/upgrade/dao/Upgrade2214to30.java index 817231fa900..2d77429367a 100644 --- a/server/src/com/cloud/upgrade/dao/Upgrade2214to30.java +++ b/server/src/com/cloud/upgrade/dao/Upgrade2214to30.java @@ -630,7 +630,7 @@ public class Upgrade2214to30 extends Upgrade30xBase implements DbUpgrade { //XenServer try { //Get 3.0.0 or later xenserer system Vm template Id - pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name like 'systemvm-xenserver-%' and removed is null"); + pstmt = conn.prepareStatement("select max(id) from `cloud`.`vm_template` where name like 'systemvm-xenserver-%' and removed is null"); rs = pstmt.executeQuery(); if(rs.next()){ long templateId = rs.getLong(1); @@ -661,7 +661,7 @@ public class Upgrade2214to30 extends Upgrade30xBase implements DbUpgrade { s_logger.debug("Updating KVM System Vms"); try { //Get 3.0.0 or later KVM system Vm template Id - pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name like 'systemvm-kvm-%' and removed is null"); + pstmt = conn.prepareStatement("select max(id) from `cloud`.`vm_template` where name like 'systemvm-kvm-%' and removed is null"); rs = pstmt.executeQuery(); if(rs.next()){ long templateId = rs.getLong(1); @@ -692,7 +692,7 @@ public class Upgrade2214to30 extends Upgrade30xBase implements DbUpgrade { s_logger.debug("Updating VMware System Vms"); try { //Get 3.0.0 or later VMware system Vm template Id - pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name like 'systemvm-vmware-%' and removed is null"); + pstmt = conn.prepareStatement("select max(id) from `cloud`.`vm_template` where name like 'systemvm-vmware-%' and removed is null"); rs = pstmt.executeQuery(); if(rs.next()){ long templateId = rs.getLong(1);