diff --git a/api/src/com/cloud/api/commands/CreateFirewallRuleCmd.java b/api/src/com/cloud/api/commands/CreateFirewallRuleCmd.java index c940add63f6..39fec5700fa 100644 --- a/api/src/com/cloud/api/commands/CreateFirewallRuleCmd.java +++ b/api/src/com/cloud/api/commands/CreateFirewallRuleCmd.java @@ -48,7 +48,7 @@ public class CreateFirewallRuleCmd extends BaseAsyncCreateCmd implements Firewal // /////////////////////////////////////////////////// @IdentityMapper(entityTableName="user_ip_address") - @Parameter(name = ApiConstants.IP_ADDRESS_ID, type = CommandType.LONG, description = "the IP address id of the port forwarding rule") + @Parameter(name = ApiConstants.IP_ADDRESS_ID, type = CommandType.LONG, required=true, description = "the IP address id of the port forwarding rule") private Long ipAddressId; @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, required = true, description = "the protocol for the firewall rule. Valid values are TCP/UDP/ICMP.") diff --git a/patches/systemvm/debian/config/opt/cloud/bin/cloud-nic.sh b/patches/systemvm/debian/config/opt/cloud/bin/cloud-nic.sh index aa7cf4775e2..24596f794e2 100755 --- a/patches/systemvm/debian/config/opt/cloud/bin/cloud-nic.sh +++ b/patches/systemvm/debian/config/opt/cloud/bin/cloud-nic.sh @@ -30,6 +30,11 @@ unplug_nic() { rule=$(echo $rule | sed 's/\-A/\-D/') sudo iptables -t mangle $rule done + iptables-save -t nat | grep $dev | grep "\-A" | while read rule + do + rule=$(echo $rule | sed 's/\-A/\-D/') + sudo iptables -t nat $rule + done iptables-save | grep $dev | grep "\-A" | while read rule do rule=$(echo $rule | sed 's/\-A/\-D/') diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 09abee876c9..998f941729c 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -304,7 +304,7 @@ public enum Config { //disabling lb as cluster sync does not work with distributed cluster AgentLbEnable("Advanced", ManagementServer.class, Boolean.class, "agent.lb.enabled", "false", "If agent load balancing enabled in cluster setup", null), SubDomainNetworkAccess("Advanced", NetworkManager.class, Boolean.class, "allow.subdomain.network.access", "true", "Allow subdomains to use networks dedicated to their parent domain(s)", null), - UseExternalDnsServers("Advanced", NetworkManager.class, Boolean.class, "use.external.dns", "false", "Bypass internal dns, use exetrnal dns1 and dns2", null), + UseExternalDnsServers("Advanced", NetworkManager.class, Boolean.class, "use.external.dns", "false", "Bypass internal dns, use external dns1 and dns2", null), EncodeApiResponse("Advanced", ManagementServer.class, Boolean.class, "encode.api.response", "false", "Do UTF-8 encoding for the api response, false by default", null), DnsBasicZoneUpdates("Advanced", NetworkManager.class, String.class, "network.dns.basiczone.updates", "all", "This parameter can take 2 values: all (default) and pod. It defines if DHCP/DNS requests have to be send to all dhcp servers in cloudstack, or only to the one in the same pod", "all,pod"), @@ -338,8 +338,9 @@ public enum Config { ConsoleProxyServiceOffering("Advanced", ManagementServer.class, Long.class, "consoleproxy.service.offering", null, "Service offering used by console proxy; if NULL - system offering will be used", null), SecondaryStorageServiceOffering("Advanced", ManagementServer.class, Long.class, "secstorage.service.offering", null, "Service offering used by secondary storage; if NULL - system offering will be used", null), HaTag("Advanced", ManagementServer.class, String.class, "ha.tag", null, "HA tag defining that the host marked with this tag can be used for HA purposes only", null), - VpcCleanupInterval("Advanced", ManagementServer.class, Integer.class, "vpc.cleanup.interval", "3600", "The interval (in seconds) between cleanup for Inactive VPCs", null); - + VpcCleanupInterval("Advanced", ManagementServer.class, Integer.class, "vpc.cleanup.interval", "3600", "The interval (in seconds) between cleanup for Inactive VPCs", null), + VpcMaxNetworks("Advanced", ManagementServer.class, Integer.class, "vpc.max.networks", "3", "Maximum number of networks per vpc", null); + private final String _category; private final Class _componentClass; diff --git a/server/src/com/cloud/network/dao/NetworkDao.java b/server/src/com/cloud/network/dao/NetworkDao.java index 0d1db5bf778..c5e98812429 100644 --- a/server/src/com/cloud/network/dao/NetworkDao.java +++ b/server/src/com/cloud/network/dao/NetworkDao.java @@ -101,5 +101,7 @@ public interface NetworkDao extends GenericDao { List listByVpc(long vpcId); NetworkVO getPrivateNetwork(String broadcastUri, String cidr, long accountId, long zoneId); + + long countVpcNetworks(long vpcId); } diff --git a/server/src/com/cloud/network/dao/NetworkDaoImpl.java b/server/src/com/cloud/network/dao/NetworkDaoImpl.java index 90bb0166b6f..62e3d381d23 100644 --- a/server/src/com/cloud/network/dao/NetworkDaoImpl.java +++ b/server/src/com/cloud/network/dao/NetworkDaoImpl.java @@ -67,6 +67,7 @@ public class NetworkDaoImpl extends GenericDaoBase implements N private final GenericSearchBuilder NetworksCount; final SearchBuilder SourceNATSearch; final GenericSearchBuilder CountByZoneAndURI; + final GenericSearchBuilder VpcNetworksCount; ResourceTagsDaoImpl _tagsDao = ComponentLocator.inject(ResourceTagsDaoImpl.class); @@ -190,6 +191,11 @@ public class NetworkDaoImpl extends GenericDaoBase implements N join6.and("service", join6.entity().getService(), Op.EQ); SourceNATSearch.join("services", join6, SourceNATSearch.entity().getId(), join6.entity().getNetworkId(), JoinBuilder.JoinType.INNER); SourceNATSearch.done(); + + VpcNetworksCount = createSearchBuilder(Long.class); + VpcNetworksCount.and("vpcId", VpcNetworksCount.entity().getVpcId(), Op.EQ); + VpcNetworksCount.select(null, Func.COUNT, VpcNetworksCount.entity().getId()); + VpcNetworksCount.done(); } @@ -531,4 +537,11 @@ public class NetworkDaoImpl extends GenericDaoBase implements N txn.commit(); return result; } + + @Override + public long countVpcNetworks(long vpcId) { + SearchCriteria sc = VpcNetworksCount.create(); + sc.setParameters("vpcId", vpcId); + return customSearch(sc, null).get(0); + } } diff --git a/server/src/com/cloud/network/vpc/VpcManager.java b/server/src/com/cloud/network/vpc/VpcManager.java index f6aa6ee7773..ee59025fcc4 100644 --- a/server/src/com/cloud/network/vpc/VpcManager.java +++ b/server/src/com/cloud/network/vpc/VpcManager.java @@ -18,7 +18,6 @@ import java.util.Set; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.ResourceUnavailableException; -import com.cloud.network.IpAddress; import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; import com.cloud.network.element.VpcProvider; @@ -110,4 +109,9 @@ public interface VpcManager extends VpcService{ * @return */ VpcGateway getPrivateGatewayForVpc(long vpcId); + + /** + * @return + */ + int getMaxNetworksPerVpc(); } diff --git a/server/src/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/com/cloud/network/vpc/VpcManagerImpl.java index 528f4b19185..263c47d65f6 100644 --- a/server/src/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/com/cloud/network/vpc/VpcManagerImpl.java @@ -45,7 +45,6 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.UnsupportedServiceException; import com.cloud.network.IPAddressVO; -import com.cloud.network.IpAddress; import com.cloud.network.Network; import com.cloud.network.Network.GuestType; import com.cloud.network.Network.Provider; @@ -150,11 +149,11 @@ public class VpcManagerImpl implements VpcManager, Manager{ FirewallRulesDao _firewallDao; private final ScheduledExecutorService _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("VpcChecker")); - private VpcProvider vpcElement = null; String _name; int _cleanupInterval; + int _maxNetworks; @Override @DB @@ -192,7 +191,9 @@ public class VpcManagerImpl implements VpcManager, Manager{ Map configs = configDao.getConfiguration(params); String value = configs.get(Config.VpcCleanupInterval.key()); _cleanupInterval = NumbersUtil.parseInt(value, 60 * 60); // 1 hour - + + String maxNtwks = configs.get(Config.VpcMaxNetworks.key()); + _maxNetworks = NumbersUtil.parseInt(maxNtwks, 3); // max=3 is default return true; } @@ -972,6 +973,13 @@ public class VpcManagerImpl implements VpcManager, Manager{ } try { + //check number of active networks in vpc + if (_ntwkDao.countVpcNetworks(vpc.getId()) >= _maxNetworks) { + throw new CloudRuntimeException("Number of networks per VPC can't extend " + + _maxNetworks + "; increase it using global config " + Config.VpcMaxNetworks); + } + + //1) CIDR is required if (cidr == null) { throw new InvalidParameterValueException("Gateway/netmask are required when create network for VPC", null); @@ -1673,4 +1681,8 @@ public class VpcManagerImpl implements VpcManager, Manager{ return _vpcGatewayDao.getPrivateGatewayForVpc(vpcId); } + public int getMaxNetworksPerVpc() { + return _maxNetworks; + } + } diff --git a/setup/db/db/schema-2214to30.sql b/setup/db/db/schema-2214to30.sql index cfaa36a071e..6584db217c1 100755 --- a/setup/db/db/schema-2214to30.sql +++ b/setup/db/db/schema-2214to30.sql @@ -742,6 +742,6 @@ UPDATE `cloud`.`guest_os` SET category_id=4 where id=131; UPDATE `cloud`.`networks` n SET n.name=(CONCAT('guestNetworkForBasicZone_', (SELECT name from `cloud`.`data_center` d where d.id=n.data_center_id AND d.networktype='Basic'))) where n.name is null and n.traffic_type='Guest'; UPDATE `cloud`.`networks` n SET n.display_text=(CONCAT('guestNetworkForBasicZone_', (SELECT name from `cloud`.`data_center` d where d.id=n.data_center_id AND d.networktype='Basic'))) where n.display_text is null and n.traffic_type='Guest'; -UPDATE `cloud`.`configuration` SET description='Bypass internal dns, use exetrnal dns1 and dns2' WHERE name='use.external.dns'; +UPDATE `cloud`.`configuration` SET description='Bypass internal dns, use external dns1 and dns2' WHERE name='use.external.dns'; UPDATE `cloud`.`configuration` SET category='Alert' WHERE name='capacity.check.period'; UPDATE `cloud`.`vm_instance` SET vnc_password = '' where removed is not null; diff --git a/setup/db/db/schema-229to2210.sql b/setup/db/db/schema-229to2210.sql index 98f5881fb5c..48c307ef21a 100644 --- a/setup/db/db/schema-229to2210.sql +++ b/setup/db/db/schema-229to2210.sql @@ -17,7 +17,7 @@ ALTER TABLE `cloud`.`account` ADD COLUMN `network_domain` varchar(255); ALTER TABLE `cloud`.`domain` ADD COLUMN `network_domain` varchar(255); -INSERT IGNORE INTO configuration VALUES ('Advanced', 'DEFAULT', 'NetworkManager', 'use.external.dns', 'false', 'Bypass internal dns, use exetrnal dns1 and dns2'); +INSERT IGNORE INTO configuration VALUES ('Advanced', 'DEFAULT', 'NetworkManager', 'use.external.dns', 'false', 'Bypass internal dns, use external dns1 and dns2'); ALTER TABLE `cloud`.`domain_router` ADD COLUMN `is_redundant_router` int(1) unsigned NOT NULL DEFAULT 0 COMMENT 'if in redundant router mode'; ALTER TABLE `cloud`.`domain_router` ADD COLUMN `priority` int(4) unsigned COMMENT 'priority of router in the redundant router mode'; diff --git a/setup/db/db/schema-304to305.sql b/setup/db/db/schema-304to305.sql index 0a0455ef837..fb79f0fae57 100755 --- a/setup/db/db/schema-304to305.sql +++ b/setup/db/db/schema-304to305.sql @@ -170,6 +170,8 @@ ALTER TABLE `cloud`.`domain_router` ADD CONSTRAINT `fk_domain_router__vpc_id` FO ALTER TABLE `cloud`.`physical_network_service_providers` ADD COLUMN `networkacl_service_provided` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Is Network ACL service provided'; INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'vpc.cleanup.interval', '3600', 'The interval (in seconds) between cleanup for Inactive VPCs'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'vpc.max.networks', '3', 'Maximum number of networks per vpc'); + CREATE TABLE `cloud`.`counter` ( `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT COMMENT 'id', diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 3266e41cf6c..c624df8e79e 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -9467,6 +9467,29 @@ div.panel.ui-dialog div.list-view div.fixed-header { text-decoration: underline; } +.vpc-chart li.tier.loading .vm-count { + padding-right: 10px; +} + +.vpc-chart li.tier .vm-count .loading-overlay { + display: none; + width: 18px; + height: 18px; + position: absolute; + left: 94px; + top: 9px; + /*+border-radius:12px;*/ + -moz-border-radius: 12px; + -webkit-border-radius: 12px; + -khtml-border-radius: 12px; + border-radius: 12px; + background-image: url(../images/ajax-loader-small.gif); +} + +.vpc-chart li.tier.loading .vm-count .loading-overlay { + display: block; +} + .vpc-chart li.tier .vm-count:hover, .vpc-chart li.tier .title:hover { border-radius: 4px; diff --git a/ui/scripts/ui-custom/vpc.js b/ui/scripts/ui-custom/vpc.js index 3aa37510b0c..7412250b571 100644 --- a/ui/scripts/ui-custom/vpc.js +++ b/ui/scripts/ui-custom/vpc.js @@ -12,8 +12,8 @@ var elems = { vpcConfigureTooltip: function(args) { var $browser = args.$browser; - var ipAddresses = args.ipAddresses; - var gateways = args.gateways; + var ipAddresses = args.ipAddresses; + var gateways = args.gateways; var siteToSiteVPN = args.siteToSiteVPN; var links = { 'ip-addresses': 'IP Addresses', @@ -34,39 +34,39 @@ $link.appendTo($links); // Link event - $link.click(function() { + $link.click(function() { switch (id) { - case 'ip-addresses': - $browser.cloudBrowser('addPanel', { - title: 'IP Addresses', - maximizeIfSelected: true, - complete: function($panel) { - //ipAddresses.listView is a function - $panel.listView(ipAddresses.listView(), {context: ipAddresses.context}); - } - }); - break; - case 'gateways': - $browser.cloudBrowser('addPanel', { - title: 'Gateways', - maximizeIfSelected: true, - complete: function($panel) { - //ipAddresses.listView is a function - $panel.listView(gateways.listView(), {context: gateways.context}); - } - }); - break; - case 'site-to-site-vpn': + case 'ip-addresses': $browser.cloudBrowser('addPanel', { - title: 'Site-to-site VPNs', + title: 'IP Addresses', maximizeIfSelected: true, - complete: function($panel) { - //siteToSiteVPN is an object - $panel.listView(siteToSiteVPN, {context: siteToSiteVPN.context}); + complete: function($panel) { + //ipAddresses.listView is a function + $panel.listView(ipAddresses.listView(), {context: ipAddresses.context}); } }); break; - } + case 'gateways': + $browser.cloudBrowser('addPanel', { + title: 'Gateways', + maximizeIfSelected: true, + complete: function($panel) { + //ipAddresses.listView is a function + $panel.listView(gateways.listView(), {context: gateways.context}); + } + }); + break; + case 'site-to-site-vpn': + $browser.cloudBrowser('addPanel', { + title: 'Site-to-site VPNs', + maximizeIfSelected: true, + complete: function($panel) { + //siteToSiteVPN is an object + $panel.listView(siteToSiteVPN, {context: siteToSiteVPN.context}); + } + }); + break; + } }); }); @@ -90,11 +90,11 @@ return $tooltip; }, - vpcConfigureArea: function(args) { + vpcConfigureArea: function(args) { var $browser = args.$browser; var ipAddresses = args.ipAddresses; - var gateways = args.gateways; - var siteToSiteVPN = args.siteToSiteVPN; + var gateways = args.gateways; + var siteToSiteVPN = args.siteToSiteVPN; var $config = $('
').addClass('config-area'); var $configIcon = $('').addClass('icon').html(' '); @@ -104,8 +104,8 @@ $configIcon.mouseover(function() { var $tooltip = elems.vpcConfigureTooltip({ $browser: $browser, - ipAddresses: ipAddresses, - gateways: gateways, + ipAddresses: ipAddresses, + gateways: gateways, siteToSiteVPN: siteToSiteVPN }); @@ -158,6 +158,12 @@ return action.id != 'add'; }); + // Add loading indicator + $vmCount.append( + $('
').addClass('loading-overlay') + .attr('title', 'VMs are launching in this tier.') + ); + // VM count shows instance list $vmCount.click(function() { var $dialog = $('
'); @@ -265,11 +271,11 @@ return $tier; }, - chart: function(args) { + chart: function(args) { var $browser = args.$browser; var ipAddresses = args.ipAddresses; - var gateways = args.gateways; - var siteToSiteVPN = args.siteToSiteVPN; + var gateways = args.gateways; + var siteToSiteVPN = args.siteToSiteVPN; var tiers = args.tiers; var vmListView = args.vmListView; var actions = args.actions; @@ -287,8 +293,8 @@ .append( elems.vpcConfigureArea({ $browser: $browser, - ipAddresses: $.extend(ipAddresses, {context: context}), - gateways: $.extend(gateways, {context: context}), + ipAddresses: $.extend(ipAddresses, {context: context}), + gateways: $.extend(gateways, {context: context}), siteToSiteVPN: $.extend(siteToSiteVPN, {context: context}) }) ); @@ -385,6 +391,19 @@ var remove = args ? args.remove : false; var _custom = args ? args._custom : {}; + var updateVMLoadingState = function() { + var pendingVMs = $tier.data('vpc-tier-pending-vms'); + + pendingVMs = pendingVMs ? pendingVMs - 1 : 0; + + if (!pendingVMs) { + $tier.data('vpc-tier-pending-vms', 0); + $tier.removeClass('loading'); + } else { + $tier.data('vpc-tier-pending-vms', pendingVMs); + } + }; + cloudStack.ui.notifications.add( // Notification { @@ -394,38 +413,44 @@ }, // Success - function(args) { - if (actionID == 'addVM') { - // Increment VM total - var $total = $tier.find('.vm-count .total'); - var prevTotal = parseInt($total.html()); - var newTotal = prevTotal + 1; - $total.html(newTotal); - - $loading.remove(); - - var newVM = args.data; + function(args) { + if (actionID == 'addVM') { + // Increment VM total + var $total = $tier.find('.vm-count .total'); + var prevTotal = parseInt($total.html()); + var newTotal = prevTotal + 1; + var newVM = args.data; var newContext = $.extend(true, {}, context, { - vms: [newVM] - }); - filterActions({ - $actions: $actions, - actionPreFilter: actionPreFilter, - context: newContext - }); - } - - else if (actionID == 'remove') { //remove tier - $tier.remove(); - } - + vms: [newVM] + }); + + $total.html(newTotal); + + filterActions({ + $actions: $actions, + actionPreFilter: actionPreFilter, + context: newContext + }); + + updateVMLoadingState(); + } else if (actionID == 'remove') { //remove tier + $loading.remove(); + $tier.remove(); + } else { + $loading.remove(); + } + }, {}, // Error function(args) { - $loading.remove(); + if (actionID == 'addVM') { + updateVMLoadingState(); + } else { + $loading.remove(); + } } ); }; @@ -435,7 +460,11 @@ action({ context: context, complete: function(args) { - $loading.appendTo($tier); + var pendingVMs = $tier.data('vpc-tier-pending-vms'); + + pendingVMs = pendingVMs ? pendingVMs + 1 : 1; + $tier.addClass('loading'); + $tier.data('vpc-tier-pending-vms', pendingVMs); success(args); } }); @@ -445,10 +474,10 @@ action({ context: context, response: { - success: function(args) { - success($.extend(args, { - remove: true - })); + success: function(args) { + success($.extend(args, { + remove: true + })); } } }); @@ -531,13 +560,13 @@ context: context, data: args.data, response: { - success: function(args) { + success: function(args) { var tier = args.data; cloudStack.ui.notifications.add( // Notification { - desc: actions.add.label + desc: actions.add.label }, // Success @@ -565,10 +594,10 @@ } ); }, - error: function(errorMsg) { - cloudStack.dialog.notice({ message: _s(errorMsg) }); - $loading.remove(); - } + error: function(errorMsg) { + cloudStack.dialog.notice({ message: _s(errorMsg) }); + $loading.remove(); + } } }); } @@ -579,8 +608,8 @@ var vmListView = args.vmListView; var tierArgs = args.tiers; var ipAddresses = args.ipAddresses; - var gateways = args.gateways; - var siteToSiteVPN = args.siteToSiteVPN; + var gateways = args.gateways; + var siteToSiteVPN = args.siteToSiteVPN; return function(args) { var context = args.context; @@ -600,12 +629,12 @@ tierArgs.dataProvider({ context: context, response: { - success: function(args) { + success: function(args) { var tiers = args.tiers; var $chart = elems.chart({ $browser: $browser, - ipAddresses: ipAddresses, - gateways: gateways, + ipAddresses: ipAddresses, + gateways: gateways, tierDetailView: tierArgs.detailView, siteToSiteVPN: siteToSiteVPN, vmListView: vmListView,