diff --git a/client/tomcatconf/classpath.conf.in b/client/tomcatconf/classpath.conf.in index f2aeebac2a4..3ae0fb4d778 100644 --- a/client/tomcatconf/classpath.conf.in +++ b/client/tomcatconf/classpath.conf.in @@ -36,11 +36,3 @@ done export CLASSPATH PATH=/sbin:/usr/sbin:$PATH export PATH - -#catalina.out owned by `cloud` not `root` -if [ ! -f $TOMCAT_LOG ]; then - touch $TOMCAT_LOG - chown $TOMCAT_USER:$TOMCAT_USER $TOMCAT_LOG -else - chown $TOMCAT_USER:$TOMCAT_USER $TOMCAT_LOG -fi diff --git a/docs/en-US/CloudStack_GSoC_Guide.ent b/docs/en-US/CloudStack_GSoC_Guide.ent new file mode 100644 index 00000000000..17415873334 --- /dev/null +++ b/docs/en-US/CloudStack_GSoC_Guide.ent @@ -0,0 +1,22 @@ + + + + + + diff --git a/docs/en-US/CloudStack_GSoC_Guide.xml b/docs/en-US/CloudStack_GSoC_Guide.xml new file mode 100644 index 00000000000..91c2967fc45 --- /dev/null +++ b/docs/en-US/CloudStack_GSoC_Guide.xml @@ -0,0 +1,52 @@ + + +%BOOK_ENTITIES; + +%xinclude; +]> + + + + + + &PRODUCT; Guide for the 2013 Google Summer of Code + Apache CloudStack + 4.3.0 + 1 + + + + Guide for 2013 Google Summer of Code Projects. + + + + + + + + + + + + + + + + diff --git a/docs/en-US/gsoc-tuna.xml b/docs/en-US/gsoc-tuna.xml new file mode 100644 index 00000000000..68032a8d46d --- /dev/null +++ b/docs/en-US/gsoc-tuna.xml @@ -0,0 +1,28 @@ + + +%BOOK_ENTITIES; +]> + + + + + Nguyen's 2013 GSoC Proposal + This chapter describes Nguyen 2013 Google Summer of Code project within the &PRODUCT; ASF project. It is a copy paste of the submitted proposal. + diff --git a/docs/en-US/multiple-ip-range.xml b/docs/en-US/multiple-ip-range.xml index a8d68c00911..42e0c2a9555 100644 --- a/docs/en-US/multiple-ip-range.xml +++ b/docs/en-US/multiple-ip-range.xml @@ -27,7 +27,8 @@ Basic zones and security groups-enabled Advanced zones. For security groups-enabled Advanced zones, it implies multiple subnets can be added to the same VLAN. With the addition of this feature, you will be able to add IP address ranges from the same subnet or from a different one - when IP address are exhausted. To support this feature, the capability of + when IP address are exhausted. This would in turn allows you to employ higher number of subnets + and thus reduce the address management overhead. To support this feature, the capability of createVlanIpRange API is extended to add IP ranges also from a different subnet. Ensure that you manually configure the gateway of the new subnet before adding the IP range. diff --git a/docs/en-US/pvlan.xml b/docs/en-US/pvlan.xml new file mode 100644 index 00000000000..96c1a78a85d --- /dev/null +++ b/docs/en-US/pvlan.xml @@ -0,0 +1,57 @@ + + +%BOOK_ENTITIES; +]> + + +
+ Isolation in Advanced Zone Using Private VLAN + +
+ About Private VLAN + In an Ethernet switch, a VLAN is a broadcast domain in which hosts can establish direct + communication with each another at Layer 2. Private VLAN is designed as an extension of VLAN + standard to add further segmentation of the logical broadcast domain. A regular VLAN is a + single broadcast domain, whereas a private VLAN partitions a larger VLAN broadcast domain into + smaller sub-domains. A sub-domain is represented by a pair of VLANs: a Primary VLAN and a + Secondary VLAN. The original VLAN that is being divided into smaller groups is called + Primary, That implies all VLAN pairs in a private VLAN share the same Primary VLAN. All the + secondary VLANs exist only inside the Primary. Each Secondary VLAN has a specific VLAN ID + associated to it, which differentiates one sub-domain from another. + For further reading: + + + Understanding Private VLANs + + + Cisco Systems' Private VLANs: Scalable + Security in a Multi-Client Environment + + + Private VLAN (PVLAN) on vNetwork Distributed Switch + - Concept Overview (1010691) + + +
+
+ Prerequisites + Ensure that you configure private VLAN on your physical switches out-of-band. +
+
diff --git a/docs/publican-gsoc-2013.cfg b/docs/publican-gsoc-2013.cfg new file mode 100644 index 00000000000..35dc517be12 --- /dev/null +++ b/docs/publican-gsoc-2013.cfg @@ -0,0 +1,27 @@ +# Publican configuration file for CloudStack Complete Documentation Set +# Contains all technical docs except release notes +# Config::Simple 4.58 +# Tue May 29 00:57:27 2012 +# +# 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. + +xml_lang: en-US +type: Book +docname: CloudStack_GSoC_Guide +brand: cloudstack +chunk_first: 1 +chunk_section_depth: 1 diff --git a/engine/schema/src/com/cloud/vm/dao/UserVmDao.java b/engine/schema/src/com/cloud/vm/dao/UserVmDao.java index e7cd61bddfe..b4f9991c99b 100755 --- a/engine/schema/src/com/cloud/vm/dao/UserVmDao.java +++ b/engine/schema/src/com/cloud/vm/dao/UserVmDao.java @@ -54,10 +54,9 @@ public interface UserVmDao extends GenericDao { /** * List user vm instances with virtualized networking (i.e. not direct attached networking) for the given account and datacenter * @param accountId will search for vm instances belonging to this account - * @param dcId will search for vm instances in this zone * @return the list of vm instances owned by the account in the given data center that have virtualized networking (not direct attached networking) */ - List listVirtualNetworkInstancesByAcctAndZone(long accountId, long dcId, long networkId); + List listVirtualNetworkInstancesByAcctAndNetwork(long accountId, long networkId); List listByNetworkIdAndStates(long networkId, State... states); diff --git a/engine/schema/src/com/cloud/vm/dao/UserVmDaoImpl.java b/engine/schema/src/com/cloud/vm/dao/UserVmDaoImpl.java index 5e8be1054a9..1c11563b270 100755 --- a/engine/schema/src/com/cloud/vm/dao/UserVmDaoImpl.java +++ b/engine/schema/src/com/cloud/vm/dao/UserVmDaoImpl.java @@ -283,11 +283,10 @@ public class UserVmDaoImpl extends GenericDaoBase implements Use } @Override - public List listVirtualNetworkInstancesByAcctAndZone(long accountId, long dcId, long networkId) { + public List listVirtualNetworkInstancesByAcctAndNetwork(long accountId, long networkId) { SearchCriteria sc = AccountDataCenterVirtualSearch.create(); sc.setParameters("account", accountId); - sc.setParameters("dc", dcId); sc.setJoinParameters("nicSearch", "networkId", networkId); return listBy(sc); diff --git a/packaging/centos63/cloud.spec b/packaging/centos63/cloud.spec index 83ccae8c582..1f112ddd686 100644 --- a/packaging/centos63/cloud.spec +++ b/packaging/centos63/cloud.spec @@ -216,6 +216,8 @@ ln -sf /var/log/%{name}/management ${RPM_BUILD_ROOT}%{_datadir}/%{name}-manageme ln -sf /var/cache/%{name}/management/temp ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/temp ln -sf /var/cache/%{name}/management/work ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/work +/bin/touch ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/management/catalina.out + install -D client/target/utilities/bin/cloud-migrate-databases ${RPM_BUILD_ROOT}%{_bindir}/%{name}-migrate-databases install -D client/target/utilities/bin/cloud-set-guest-password ${RPM_BUILD_ROOT}%{_bindir}/%{name}-set-guest-password install -D client/target/utilities/bin/cloud-set-guest-sshkey ${RPM_BUILD_ROOT}%{_bindir}/%{name}-set-guest-sshkey @@ -519,6 +521,7 @@ fi %dir %attr(0770,root,root) %{_localstatedir}/log/%{name}-management %{_defaultdocdir}/%{name}-management-%{version}/LICENSE %{_defaultdocdir}/%{name}-management-%{version}/NOTICE +%attr(0644,cloud,cloud) %{_localstatedir}/log/%{name}/management/catalina.out %files agent %attr(0755,root,root) %{_bindir}/%{name}-setup-agent diff --git a/server/src/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java b/server/src/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java index d52859bd4c0..67d31ab3a4e 100755 --- a/server/src/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java +++ b/server/src/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java @@ -1835,9 +1835,8 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements } } - IPAddressVO addr = _ipAddressDao.findById(loadBalancer.getSourceIpAddressId()); - List userVms = _vmDao.listVirtualNetworkInstancesByAcctAndZone(loadBalancer.getAccountId(), - addr.getDataCenterId(), loadBalancer.getNetworkId()); + List userVms = _vmDao.listVirtualNetworkInstancesByAcctAndNetwork(loadBalancer.getAccountId(), + loadBalancer.getNetworkId()); for (UserVmVO userVm : userVms) { // if the VM is destroyed, being expunged, in an error state, or in diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index cf50e61d6ac..96c72e4a4e2 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -30,6 +30,7 @@ import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -373,15 +374,6 @@ 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.RevertToVMSnapshotCmd; import org.apache.cloudstack.api.command.user.volume.*; -import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.DeleteVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.ListVolumesCmd; -import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; -import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd; import org.apache.cloudstack.api.command.user.vpc.CreateStaticRouteCmd; import org.apache.cloudstack.api.command.user.vpc.CreateVPCCmd; import org.apache.cloudstack.api.command.user.vpc.DeleteStaticRouteCmd; @@ -1184,10 +1176,11 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe allHosts.remove(srcHost); // Check if the host has storage pools for all the volumes of the vm to be migrated. - for (Host host : allHosts) { + for (Iterator iterator = allHosts.iterator(); iterator.hasNext();) { + Host host = iterator.next(); Map> volumePools = findSuitablePoolsForVolumes(vmProfile, host); if (volumePools.isEmpty()) { - allHosts.remove(host); + iterator.remove(); } else { if (!host.getClusterId().equals(srcHost.getClusterId()) || usesLocal) { requiresStorageMotion.put(host, true); diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 8cf05aa7761..d8a064f5987 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -755,6 +755,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } if (vm.getState() == State.Running && vm.getHostId() != null) { + collectVmDiskStatistics(vm); return _itMgr.reboot(vm, null, caller, owner); } else { s_logger.error("Vm id=" + vmId @@ -3379,9 +3380,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use boolean status; State vmState = vm.getState(); - // Collect vm disk statistics from host before stopping Vm - collectVmDiskStatistics(vm); - try { VirtualMachineEntity vmEntity = _orchSrvc.getVirtualMachine(vm.getUuid()); status = vmEntity.destroy(new Long(userId).toString()); @@ -3830,7 +3828,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use "No permission to migrate VM, Only Root Admin can migrate a VM!"); } - VMInstanceVO vm = _vmInstanceDao.findById(vmId); + UserVmVO vm = _vmDao.findById(vmId); if (vm == null) { throw new InvalidParameterValueException( "Unable to find the VM by id=" + vmId); @@ -3921,6 +3919,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use + " already has max Running VMs(count includes system VMs), cannot migrate to this host"); } + collectVmDiskStatistics(vm); VMInstanceVO migratedVm = _itMgr.migrate(vm, srcHostId, dest); return migratedVm; } @@ -4710,6 +4709,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use @Override public void prepareStop(VirtualMachineProfile profile) { + UserVmVO vm = profile.getVirtualMachine(); + if (vm.getState() == State.Running) + collectVmDiskStatistics(vm); } } diff --git a/test/integration/smoke/test_internal_lb.py b/test/integration/smoke/test_internal_lb.py index 07a539592ca..0de2d4cf338 100644 --- a/test/integration/smoke/test_internal_lb.py +++ b/test/integration/smoke/test_internal_lb.py @@ -25,228 +25,163 @@ from marvin.integration.lib.common import * from nose.plugins.attrib import attr +class Services: + def __init__(self): + self.services = { + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + "password": "password", + }, + "virtual_machine": { + "displayname": "Test VM", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "ostype": 'CentOS 5.3 (64-bit)', + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 100, + "memory": 256, + }, + "network_offering": { + "name": "Network offering for internal lb service", + "displaytext": "Network offering for internal lb service", + "guestiptype": "Isolated", + "traffictype": "Guest", + "supportedservices": "Vpn,Dhcp,Dns,Lb,UserData,SourceNat,StaticNat,PortForwarding,NetworkACL", + "serviceProviderList": { + "Dhcp": "VpcVirtualRouter", + "Dns": "VpcVirtualRouter", + "Vpn": "VpcVirtualRouter", + "UserData": "VpcVirtualRouter", + "Lb": "InternalLbVM", + "SourceNat": "VpcVirtualRouter", + "StaticNat": "VpcVirtualRouter", + "PortForwarding": "VpcVirtualRouter", + "NetworkACL": "VpcVirtualRouter", + }, + "serviceCapabilityList": { + "SourceNat": {"SupportedSourceNatTypes": "peraccount"}, + "Lb": {"lbSchemes": "internal", "SupportedLbIsolation": "dedicated"} + } + } + } + + class TestInternalLb(cloudstackTestCase): - networkOfferingId = None - networkId = None - vmId = None - lbId = None + """Test Internal LB + """ - zoneId = 1 - serviceOfferingId = 1 - templateId = 5 + @classmethod + def setUpClass(cls): + cls.apiclient = super(TestInternalLb, cls).getClsTestClient().getApiClient() + cls.services = Services().services + cls.zone = get_zone(cls.apiclient, cls.services) + cls.domain = get_domain(cls.apiclient) + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offering"] + ) + cls.account = Account.create(cls.apiclient, services=cls.services["account"]) + cls.template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"] + ) + cls.debug("Successfully created account: %s, id: \ + %s" % (cls.account.name,\ + cls.account.id)) + cls.cleanup = [cls.account] - - serviceProviderList = [ - { - "provider": "VpcVirtualRouter", - "service": "Vpn" - }, - { - "provider": "VpcVirtualRouter", - "service": "UserData" - }, - { - "provider": "VpcVirtualRouter", - "service": "Dhcp" - }, - { - "provider": "VpcVirtualRouter", - "service": "Dns" - }, - { - "provider": "InternalLbVM", - "service": "Lb" - }, - { - "provider": "VpcVirtualRouter", - "service": "SourceNat" - }, - { - "provider": "VpcVirtualRouter", - "service": "StaticNat" - }, - { - "provider": "VpcVirtualRouter", - "service": "PortForwarding" - }, - { - "provider": "VpcVirtualRouter", - "service": "NetworkACL" - } - ] - - serviceCapsList = [ - { - "service": "SourceNat", - "capabilitytype": "SupportedSourceNatTypes", - "capabilityvalue": "peraccount" - }, - { - "service": "Lb", - "capabilitytype": "SupportedLbIsolation", - "capabilityvalue": "dedicated" - }, - { - "service": "Lb", - "capabilitytype": "lbSchemes", - "capabilityvalue": "internal" - } - ] - - def setUp(self): - self.apiClient = self.testClient.getApiClient() - - - - @attr(tags=["advanced"]) + @attr(tags=["smoke", "advanced"]) def test_internallb(self): + """Test create, delete, assign, remove of internal loadbalancer + """ #1) Create and enable network offering with Internal Lb vm service - self.createNetworkOffering() - + self.networkOffering = NetworkOffering.create(self.apiclient, self.services["network_offering"], conservemode=False) + self.networkOffering.update(self.apiclient, state="Enabled") + #2) Create VPC and network in it - self.createNetwork() - - #3) Deploy a vm - self.deployVm() + vpcOffering = VpcOffering.list(self.apiclient) + self.assert_(vpcOffering is not None and len(vpcOffering)>0, "No VPC offerings found") + self.services["vpc"] = {} + self.services["vpc"]["name"] = "vpc-internallb" + self.services["vpc"]["displaytext"] = "vpc-internallb" + self.services["vpc"]["cidr"] = "10.1.1.0/24" + vpc = VPC.create( + apiclient=self.apiclient, + services=self.services["vpc"], + networkDomain="vpc.internallb", + vpcofferingid=vpcOffering[0].id, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.domain.id + ) + self.assert_(vpc is not None, "VPC creation failed") + self.services["vpcnetwork"] = {} + self.services["vpcnetwork"]["name"] = "vpcntwk" + self.services["vpcnetwork"]["displaytext"] = "vpcntwk" + ntwk = Network.create( + apiclient=self.apiclient, + services=self.services["vpcnetwork"], + accountid=self.account.name, + domainid=self.domain.id, + networkofferingid=self.networkOffering.id, + zoneid=self.zone.id, + vpcid=vpc.id, + gateway="10.1.1.1", + netmask="255.255.255.192" + ) + self.assertIsNotNone(ntwk, "Network failed to create") + self.debug("Network %s created in VPC %s" %(ntwk.id, vpc.id)) + + #3) Deploy a vm + self.services["virtual_machine"]["networkids"] = ntwk.id + vm = VirtualMachine.create(self.apiclient, services=self.services["virtual_machine"], + templateid=self.template.id, + zoneid=self.zone.id, + accountid=self.account.name, + domainid= self.domain.id, + serviceofferingid=self.service_offering.id, + ) + self.assert_(vm is not None, "VM failed to deploy") + self.assert_(vm.state == 'Running', "VM is not running") + self.debug("VM %s deployed in VPC %s" %(vm.id, vpc.id)) #4) Create an Internal Load Balancer - self.createInternalLoadBalancer() + applb = ApplicationLoadBalancer.create(self.apiclient, services=self.services, + name="lbrule", + sourceport=22, + instanceport=22, + algorithm="roundrobin", + scheme="internal", + sourcenetworkid=ntwk.id, + networkid=ntwk.id) #5) Assign the VM to the Internal Load Balancer - self.assignToLoadBalancerRule() + applb.assign(self.apiclient, vms=[vm.id]) #6) Remove the vm from the Interanl Load Balancer - self.removeFromLoadBalancerRule() + applb.remove(self.apiclient, vms=[vm.id]) #7) Delete the Load Balancer - self.deleteLoadBalancer() + applb.delete(self.apiclient) + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.apiclient, cls.cleanup) + except Exception, e: + raise Exception("Cleanup failed with %s" % e) - def deployVm(self): - deployVirtualMachineCmd = deployVirtualMachine.deployVirtualMachineCmd() - deployVirtualMachineCmd.networkids = TestInternalLb.networkId - deployVirtualMachineCmd.serviceofferingid = TestInternalLb.serviceOfferingId - deployVirtualMachineCmd.zoneid = TestInternalLb.zoneId - deployVirtualMachineCmd.templateid = TestInternalLb.templateId - deployVirtualMachineCmd.hypervisor = "XenServer" - deployVMResponse = self.apiClient.deployVirtualMachine(deployVirtualMachineCmd) - TestInternalLb.vmId = deployVMResponse.id - - - def createInternalLoadBalancer(self): - createLoadBalancerCmd = createLoadBalancer.createLoadBalancerCmd() - createLoadBalancerCmd.name = "lb rule" - createLoadBalancerCmd.sourceport = 22 - createLoadBalancerCmd.instanceport = 22 - createLoadBalancerCmd.algorithm = "roundrobin" - createLoadBalancerCmd.scheme = "internal" - createLoadBalancerCmd.sourceipaddressnetworkid = TestInternalLb.networkId - createLoadBalancerCmd.networkid = TestInternalLb.networkId - createLoadBalancerResponse = self.apiClient.createLoadBalancer(createLoadBalancerCmd) - TestInternalLb.lbId = createLoadBalancerResponse.id - self.assertIsNotNone(createLoadBalancerResponse.id, "Failed to create a load balancer") - - - def assignToLoadBalancerRule(self): - assignToLoadBalancerRuleCmd = assignToLoadBalancerRule.assignToLoadBalancerRuleCmd() - assignToLoadBalancerRuleCmd.id = TestInternalLb.lbId - assignToLoadBalancerRuleCmd.virtualMachineIds = TestInternalLb.vmId - assignToLoadBalancerRuleResponse = self.apiClient.assignToLoadBalancerRule(assignToLoadBalancerRuleCmd) - self.assertTrue(assignToLoadBalancerRuleResponse.success, "Failed to assign the vm to the load balancer") - - - - def removeFromLoadBalancerRule(self): - removeFromLoadBalancerRuleCmd = removeFromLoadBalancerRule.removeFromLoadBalancerRuleCmd() - removeFromLoadBalancerRuleCmd.id = TestInternalLb.lbId - removeFromLoadBalancerRuleCmd.virtualMachineIds = TestInternalLb.vmId - removeFromLoadBalancerRuleResponse = self.apiClient.removeFromLoadBalancerRule(removeFromLoadBalancerRuleCmd) - self.assertTrue(removeFromLoadBalancerRuleResponse.success, "Failed to remove the vm from the load balancer") - - - - #def removeInternalLoadBalancer(self): - def deleteLoadBalancer(self): - deleteLoadBalancerCmd = deleteLoadBalancer.deleteLoadBalancerCmd() - deleteLoadBalancerCmd.id = TestInternalLb.lbId - deleteLoadBalancerResponse = self.apiClient.deleteLoadBalancer(deleteLoadBalancerCmd) - self.assertTrue(deleteLoadBalancerResponse.success, "Failed to remove the load balancer") - - - - def createNetwork(self): - createVPCCmd = createVPC.createVPCCmd() - createVPCCmd.name = "new vpc" - createVPCCmd.cidr = "10.1.1.0/24" - createVPCCmd.displaytext = "new vpc" - createVPCCmd.vpcofferingid = 1 - createVPCCmd.zoneid = self.zoneId - createVPCResponse = self.apiClient.createVPC(createVPCCmd) - - - createNetworkCmd = createNetwork.createNetworkCmd() - createNetworkCmd.name = "vpc network" - createNetworkCmd.displaytext = "vpc network" - createNetworkCmd.netmask = "255.255.255.0" - createNetworkCmd.gateway = "10.1.1.1" - createNetworkCmd.zoneid = self.zoneId - createNetworkCmd.vpcid = createVPCResponse.id - createNetworkCmd.networkofferingid = TestInternalLb.networkOfferingId - createNetworkResponse = self.apiClient.createNetwork(createNetworkCmd) - TestInternalLb.networkId = createNetworkResponse.id - - self.assertIsNotNone(createNetworkResponse.id, "Network failed to create") - - - def createNetworkOffering(self): - createNetworkOfferingCmd = createNetworkOffering.createNetworkOfferingCmd() - createNetworkOfferingCmd.name = "Network offering for internal lb service - " + str(random.randrange(1,100+1)) - createNetworkOfferingCmd.displaytext = "Network offering for internal lb service" - createNetworkOfferingCmd.guestiptype = "isolated" - createNetworkOfferingCmd.traffictype = "Guest" - createNetworkOfferingCmd.conservemode = "false" - createNetworkOfferingCmd.supportedservices = "Vpn,Dhcp,Dns,Lb,UserData,SourceNat,StaticNat,PortForwarding,NetworkACL" - - - createNetworkOfferingCmd.serviceproviderlist = [] - for item in self.serviceProviderList: - createNetworkOfferingCmd.serviceproviderlist.append({ - 'service': item['service'], - 'provider': item['provider'] - }) - - createNetworkOfferingCmd.servicecapabilitylist = [] - for item in self.serviceCapsList: - createNetworkOfferingCmd.servicecapabilitylist.append({ - 'service': item['service'], - 'capabilitytype': item['capabilitytype'], - 'capabilityvalue': item['capabilityvalue'] - }) - - - createNetworkOfferingResponse = self.apiClient.createNetworkOffering(createNetworkOfferingCmd) - TestInternalLb.networkOfferingId = createNetworkOfferingResponse.id - - #enable network offering - updateNetworkOfferingCmd = updateNetworkOffering.updateNetworkOfferingCmd() - updateNetworkOfferingCmd.id = TestInternalLb.networkOfferingId - updateNetworkOfferingCmd.state = "Enabled" - updateNetworkOfferingResponse = self.apiClient.updateNetworkOffering(updateNetworkOfferingCmd) - - - #list network offering to see if its enabled - listNetworkOfferingsCmd = listNetworkOfferings.listNetworkOfferingsCmd() - listNetworkOfferingsCmd.id = TestInternalLb.networkOfferingId - listOffResponse = self.apiClient.listNetworkOfferings(listNetworkOfferingsCmd) - - self.assertNotEqual(len(listOffResponse), 0, "Check if the list network offerings API \ - returns a non-empty response") - - - def tearDown(self): - #destroy the vm - if TestInternalLb.vmId is not None: - destroyVirtualMachineCmd = destroyVirtualMachine.destroyVirtualMachineCmd() - destroyVirtualMachineCmd.id = TestInternalLb.vmId - destroyVirtualMachineResponse = self.apiClient.destroyVirtualMachine(destroyVirtualMachineCmd) diff --git a/test/integration/smoke/test_iso.py b/test/integration/smoke/test_iso.py index c645d3b055d..75289b8fbe3 100644 --- a/test/integration/smoke/test_iso.py +++ b/test/integration/smoke/test_iso.py @@ -485,7 +485,7 @@ class TestISO(cloudstackTestCase): if len(self.zones) <= 1: self.skipTest("Not enough zones available to perform copy template") - self.services["destzoneid"] = filter(lambda z: z.id != self.zone.id, self.zones)[0] + self.services["destzoneid"] = filter(lambda z: z.id != self.zone.id, self.zones)[0].id self.debug("Copy ISO from %s to %s" % ( self.zone.id, diff --git a/test/integration/smoke/test_network_acl.py b/test/integration/smoke/test_network_acl.py index 3ed45be9cd0..4b3c1f70b67 100644 --- a/test/integration/smoke/test_network_acl.py +++ b/test/integration/smoke/test_network_acl.py @@ -24,96 +24,154 @@ from marvin.integration.lib.base import * from marvin.integration.lib.common import * from nose.plugins.attrib import attr +class Services: + def __init__(self): + self.services = { + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + "password": "password", + }, + "virtual_machine": { + "displayname": "Test VM", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "ostype": 'CentOS 5.3 (64-bit)', + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 100, + "memory": 256, + }, + "network_offering": { + "name": "Network offering for internal lb service", + "displaytext": "Network offering for internal lb service", + "guestiptype": "Isolated", + "traffictype": "Guest", + "supportedservices": "Vpn,Dhcp,Dns,Lb,UserData,SourceNat,StaticNat,PortForwarding,NetworkACL", + "serviceProviderList": { + "Dhcp": "VpcVirtualRouter", + "Dns": "VpcVirtualRouter", + "Vpn": "VpcVirtualRouter", + "UserData": "VpcVirtualRouter", + "Lb": "InternalLbVM", + "SourceNat": "VpcVirtualRouter", + "StaticNat": "VpcVirtualRouter", + "PortForwarding": "VpcVirtualRouter", + "NetworkACL": "VpcVirtualRouter", + }, + "serviceCapabilityList": { + "SourceNat": {"SupportedSourceNatTypes": "peraccount"}, + "Lb": {"lbSchemes": "internal", "SupportedLbIsolation": "dedicated"} + } + } + } + + class TestNetworkACL(cloudstackTestCase): - networkOfferingId = 11 - networkId = None - vmId = None - vpcId = None - aclId = None - zoneId = 1 - serviceOfferingId = 1 - templateId = 5 + @classmethod + def setUpClass(cls): + cls.apiclient = super(TestNetworkACL, cls).getClsTestClient().getApiClient() + cls.services = Services().services + cls.zone = get_zone(cls.apiclient, cls.services) + cls.domain = get_domain(cls.apiclient) + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offering"] + ) + cls.account = Account.create(cls.apiclient, services=cls.services["account"]) + cls.template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"] + ) + cls.debug("Successfully created account: %s, id: \ + %s" % (cls.account.name,\ + cls.account.id)) + cls.cleanup = [cls.account] - def setUp(self): - self.apiClient = self.testClient.getApiClient() - - - @attr(tags=["advanced"]) - def test_networkAcl(self): + def test_network_acl(self): + """Test network ACL lists and items in VPC""" + + # 0) Get the default network offering for VPC + networkOffering = NetworkOffering.list(self.apiclient, name="DefaultIsolatedNetworkOfferingForVpcNetworks") + self.assert_(networkOffering is not None and len(networkOffering) > 0, "No VPC based network offering") # 1) Create VPC - self.createVPC() + vpcOffering = VpcOffering.list(self.apiclient) + self.assert_(vpcOffering is not None and len(vpcOffering)>0, "No VPC offerings found") + self.services["vpc"] = {} + self.services["vpc"]["name"] = "vpc-networkacl" + self.services["vpc"]["displaytext"] = "vpc-networkacl" + self.services["vpc"]["cidr"] = "10.1.1.0/24" + vpc = VPC.create( + apiclient=self.apiclient, + services=self.services["vpc"], + networkDomain="vpc.networkacl", + vpcofferingid=vpcOffering[0].id, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.domain.id + ) + self.assert_(vpc is not None, "VPC creation failed") - # 2) Create ACl - self.createACL() + # 2) Create ACL + aclgroup = NetworkACLList.create(apiclient=self.apiclient, services={}, name="acl", description="acl", vpcid=vpc.id) + self.assertIsNotNone(aclgroup, "Failed to create NetworkACL list") + self.debug("Created a network ACL list %s" % aclgroup.name) - # 3) Create ACl Item - self.createACLItem() + # 3) Create ACL Item + aclitem = NetworkACL.create(apiclient=self.apiclient, services={}, + protocol="TCP", number="10", action="Deny", aclid=aclgroup.id, cidrlist=["0.0.0.0/0"]) + self.assertIsNotNone(aclitem, "Network failed to aclItem") + self.debug("Added a network ACL %s to ACL list %s" % (aclitem.id, aclgroup.name)) # 4) Create network with ACL - self.createNetwork() + self.services["vpcnetwork"] = {} + self.services["vpcnetwork"]["name"] = "vpcntwk" + self.services["vpcnetwork"]["displaytext"] = "vpcntwk" + ntwk = Network.create( + apiclient=self.apiclient, + services=self.services["vpcnetwork"], + accountid=self.account.name, + domainid=self.domain.id, + networkofferingid=networkOffering[0].id, + zoneid=self.zone.id, + vpcid=vpc.id, + aclid=aclgroup.id, + gateway="10.1.1.1", + netmask="255.255.255.192" + ) + self.assertIsNotNone(ntwk, "Network failed to create") + self.debug("Network %s created in VPC %s" %(ntwk.id, vpc.id)) + # 5) Deploy a vm - self.deployVm() + self.services["virtual_machine"]["networkids"] = ntwk.id + vm = VirtualMachine.create(self.apiclient, services=self.services["virtual_machine"], + templateid=self.template.id, + zoneid=self.zone.id, + accountid=self.account.name, + domainid= self.domain.id, + serviceofferingid=self.service_offering.id, + ) + self.assert_(vm is not None, "VM failed to deploy") + self.assert_(vm.state == 'Running', "VM is not running") + self.debug("VM %s deployed in VPC %s" %(vm.id, vpc.id)) - def createACL(self): - createAclCmd = createNetworkACLList.createNetworkACLListCmd() - createAclCmd.name = "acl1" - createAclCmd.description = "new acl" - createAclCmd.vpcId = TestNetworkACL.vpcId - createAclResponse = self.apiClient.createNetworkACLList(createAclCmd) - TestNetworkACL.aclId = createAclResponse.id - - def createACLItem(self): - createAclItemCmd = createNetworkACL.createNetworkACLCmd() - createAclItemCmd.cidr = "0.0.0.0/0" - createAclItemCmd.protocol = "TCP" - createAclItemCmd.number = "10" - createAclItemCmd.action = "Deny" - createAclItemCmd.aclId = TestNetworkACL.aclId - createAclItemResponse = self.apiClient.createNetworkACL(createAclItemCmd) - self.assertIsNotNone(createAclItemResponse.id, "Network failed to aclItem") - - def createVPC(self): - createVPCCmd = createVPC.createVPCCmd() - createVPCCmd.name = "new vpc" - createVPCCmd.cidr = "10.1.1.0/24" - createVPCCmd.displaytext = "new vpc" - createVPCCmd.vpcofferingid = 1 - createVPCCmd.zoneid = self.zoneId - createVPCResponse = self.apiClient.createVPC(createVPCCmd) - TestNetworkACL.vpcId = createVPCResponse.id - - - def createNetwork(self): - createNetworkCmd = createNetwork.createNetworkCmd() - createNetworkCmd.name = "vpc network" - createNetworkCmd.displaytext = "vpc network" - createNetworkCmd.netmask = "255.255.255.0" - createNetworkCmd.gateway = "10.1.1.1" - createNetworkCmd.zoneid = self.zoneId - createNetworkCmd.vpcid = TestNetworkACL.vpcId - createNetworkCmd.networkofferingid = TestNetworkACL.networkOfferingId - createNetworkCmd.aclId = TestNetworkACL.aclId - createNetworkResponse = self.apiClient.createNetwork(createNetworkCmd) - TestNetworkACL.networkId = createNetworkResponse.id - - self.assertIsNotNone(createNetworkResponse.id, "Network failed to create") - - def deployVm(self): - deployVirtualMachineCmd = deployVirtualMachine.deployVirtualMachineCmd() - deployVirtualMachineCmd.networkids = TestNetworkACL.networkId - deployVirtualMachineCmd.serviceofferingid = TestNetworkACL.serviceOfferingId - deployVirtualMachineCmd.zoneid = TestNetworkACL.zoneId - deployVirtualMachineCmd.templateid = TestNetworkACL.templateId - deployVirtualMachineCmd.hypervisor = "XenServer" - deployVMResponse = self.apiClient.deployVirtualMachine(deployVirtualMachineCmd) - TestNetworkACL.vmId = deployVMResponse.id - - def tearDown(self): - #destroy the vm - if TestNetworkACL.vmId is not None: - destroyVirtualMachineCmd = destroyVirtualMachine.destroyVirtualMachineCmd() - destroyVirtualMachineCmd.id = TestNetworkACL.vmId - destroyVirtualMachineResponse = self.apiClient.destroyVirtualMachine(destroyVirtualMachineCmd) + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.apiclient, cls.cleanup) + except Exception, e: + raise Exception("Cleanup failed with %s" % e) diff --git a/test/integration/smoke/test_service_offerings.py b/test/integration/smoke/test_service_offerings.py index 3d3b94684e8..a56e34d2874 100644 --- a/test/integration/smoke/test_service_offerings.py +++ b/test/integration/smoke/test_service_offerings.py @@ -34,6 +34,15 @@ class Services: def __init__(self): self.services = { + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended in create account to + # ensure unique username generated each time + "password": "password", + }, "off": { "name": "Service Offering", diff --git a/test/integration/smoke/test_templates.py b/test/integration/smoke/test_templates.py index 8b83f5e6e43..9478440f77e 100644 --- a/test/integration/smoke/test_templates.py +++ b/test/integration/smoke/test_templates.py @@ -665,7 +665,7 @@ class TestTemplates(cloudstackTestCase): if len(self.zones) <= 1: self.skipTest("Not enough zones available to perform copy template") - self.services["destzoneid"] = filter(lambda z: z.id != self.services["sourcezoneid"], self.zones)[0] + self.services["destzoneid"] = filter(lambda z: z.id != self.services["sourcezoneid"], self.zones)[0].id self.debug("Copy template from Zone: %s to %s" % ( self.services["sourcezoneid"], diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py index 6b3be75e896..503ed6446f5 100755 --- a/tools/marvin/marvin/integration/lib/base.py +++ b/tools/marvin/marvin/integration/lib/base.py @@ -1371,18 +1371,18 @@ class NetworkOffering: if "useVpc" in services: cmd.useVpc = services["useVpc"] - cmd.serviceProviderList = [] + cmd.serviceproviderlist = [] if "serviceProviderList" in services: for service, provider in services["serviceProviderList"].items(): - cmd.serviceProviderList.append({ + cmd.serviceproviderlist.append({ 'service': service, 'provider': provider }) - if "servicecapabilitylist" in services: - cmd.serviceCapabilityList = [] - for service, capability in services["servicecapabilitylist"].items(): + if "serviceCapabilityList" in services: + cmd.servicecapabilitylist = [] + for service, capability in services["serviceCapabilityList"].items(): for ctype, value in capability.items(): - cmd.serviceCapabilityList.append({ + cmd.servicecapabilitylist.append({ 'service': service, 'capabilitytype': ctype, 'capabilityvalue': value @@ -1800,7 +1800,7 @@ class Network: def create(cls, apiclient, services, accountid=None, domainid=None, networkofferingid=None, projectid=None, subdomainaccess=None, zoneid=None, - gateway=None, netmask=None, vpcid=None, guestcidr=None): + gateway=None, netmask=None, vpcid=None, aclid=None, guestcidr=None): """Create Network for account""" cmd = createNetwork.createNetworkCmd() cmd.name = services["name"] @@ -1846,6 +1846,8 @@ class Network: cmd.guestcidr = guestcidr if vpcid: cmd.vpcid = vpcid + if aclid: + cmd.aclid = aclid return Network(apiclient.createNetwork(cmd).__dict__) def delete(self, apiclient): @@ -1888,25 +1890,55 @@ class NetworkACL: self.__dict__.update(items) @classmethod - def create(cls, apiclient, networkid, services, traffictype=None): + def create(cls, apiclient, services, networkid=None, protocol=None, + number=None, aclid=None, action='Allow', traffictype=None, cidrlist=[]): """Create network ACL rules(Ingress/Egress)""" cmd = createNetworkACL.createNetworkACLCmd() - cmd.networkid = networkid + if "networkid" in services: + cmd.networkid = services["networkid"] + elif networkid: + cmd.networkid = networkid + if "protocol" in services: cmd.protocol = services["protocol"] + if services["protocol"] == 'ICMP': + cmd.icmptype = -1 + cmd.icmpcode = -1 + elif protocol: + cmd.protocol = protocol - if services["protocol"] == 'ICMP': - cmd.icmptype = -1 - cmd.icmpcode = -1 - else: + if "startport" in services: cmd.startport = services["startport"] + if "endport" in services: cmd.endport = services["endport"] - cmd.cidrlist = services["cidrlist"] - if traffictype: + if "cidrlist" in services: + cmd.cidrlist = services["cidrlist"] + elif cidrlist: + cmd.cidrlist = cidrlist + + if "traffictype" in services: + cmd.traffictype = services["traffictype"] + elif traffictype: cmd.traffictype = traffictype - # Defaulted to Ingress + + if "action" in services: + cmd.action = services["action"] + elif action: + cmd.action = action + + if "number" in services: + cmd.number = services["number"] + elif number: + cmd.number = number + + if "aclid" in services: + cmd.aclid = services["aclid"] + elif aclid: + cmd.aclid = aclid + + # Defaulted to Ingress return NetworkACL(apiclient.createNetworkACL(cmd).__dict__) def delete(self, apiclient): @@ -1925,6 +1957,50 @@ class NetworkACL: return(apiclient.listNetworkACLs(cmd)) +class NetworkACLList: + """Manage Network ACL lists lifecycle""" + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, name=None, description=None, vpcid=None): + """Create network ACL container list""" + + cmd = createNetworkACLList.createNetworkACLListCmd() + if "name" in services: + cmd.name = services["name"] + elif name: + cmd.name = name + + if "description" in services: + cmd.description = services["description"] + elif description: + cmd.description = description + + if "vpcid" in services: + cmd.vpcid = services["vpcid"] + elif vpcid: + cmd.vpcid = vpcid + + return NetworkACLList(apiclient.createNetworkACLList(cmd).__dict__) + + def delete(self, apiclient): + """Delete network acl list""" + + cmd = deleteNetworkACLList.deleteNetworkACLListCmd() + cmd.id = self.id + return apiclient.deleteNetworkACLList(cmd) + + @classmethod + def list(cls, apiclient, **kwargs): + """List Network ACL lists""" + + cmd = listNetworkACLLists.listNetworkACLListsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listNetworkACLLists(cmd)) + + class Vpn: """Manage VPN life cycle""" @@ -2798,7 +2874,7 @@ class VPC: @classmethod def create(cls, apiclient, services, vpcofferingid, - zoneid, networkDomain=None, account=None, domainid=None): + zoneid, networkDomain=None, account=None, domainid=None, **kwargs): """Creates the virtual private connection (VPC)""" cmd = createVPC.createVPCCmd() @@ -2806,13 +2882,15 @@ class VPC: cmd.displaytext = "-".join([services["displaytext"], random_gen()]) cmd.vpcofferingid = vpcofferingid cmd.zoneid = zoneid - cmd.cidr = services["cidr"] + if "cidr" in services: + cmd.cidr = services["cidr"] if account: cmd.account = account if domainid: cmd.domainid = domainid if networkDomain: cmd.networkDomain = networkDomain + [setattr(cmd, k, v) for k, v in kwargs.items()] return VPC(apiclient.createVPC(cmd).__dict__) def update(self, apiclient, name=None, displaytext=None): @@ -3216,3 +3294,83 @@ class Region: cmd.id = self.id region = apiclient.removeRegion(cmd) return region + + +class ApplicationLoadBalancer: + """Manage Application Load Balancers in VPC""" + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, name=None, sourceport=None, instanceport=22, + algorithm="roundrobin", scheme="internal", sourcenetworkid=None, networkid=None): + """Create Application Load Balancer""" + cmd = createLoadBalancer.createLoadBalancerCmd() + + if "name" in services: + cmd.name = services["name"] + elif name: + cmd.name = name + + if "sourceport" in services: + cmd.sourceport = services["sourceport"] + elif sourceport: + cmd.sourceport = sourceport + + if "instanceport" in services: + cmd.instanceport = services["instanceport"] + elif instanceport: + cmd.instanceport = instanceport + + if "algorithm" in services: + cmd.algorithm = services["algorithm"] + elif algorithm: + cmd.algorithm = algorithm + + if "scheme" in services: + cmd.scheme = services["scheme"] + elif scheme: + cmd.scheme = scheme + + if "sourceipaddressnetworkid" in services: + cmd.sourceipaddressnetworkid = services["sourceipaddressnetworkid"] + elif sourcenetworkid: + cmd.sourceipaddressnetworkid = sourcenetworkid + + if "networkid" in services: + cmd.networkid = services["networkid"] + elif networkid: + cmd.networkid = networkid + + return LoadBalancerRule(apiclient.createLoadBalancer(cmd).__dict__) + + def delete(self, apiclient): + """Delete application load balancer""" + cmd = deleteLoadBalancer.deleteLoadBalancerCmd() + cmd.id = self.id + apiclient.deleteLoadBalancerRule(cmd) + return + + def assign(self, apiclient, vms): + """Assign virtual machines to load balancing rule""" + cmd = assignToLoadBalancerRule.assignToLoadBalancerRuleCmd() + cmd.id = self.id + cmd.virtualmachineids = [str(vm.id) for vm in vms] + apiclient.assignToLoadBalancerRule(cmd) + return + + def remove(self, apiclient, vms): + """Remove virtual machines from load balancing rule""" + cmd = removeFromLoadBalancerRule.removeFromLoadBalancerRuleCmd() + cmd.id = self.id + cmd.virtualmachineids = [str(vm.id) for vm in vms] + apiclient.removeFromLoadBalancerRule(cmd) + return + + @classmethod + def list(cls, apiclient, **kwargs): + """List all appln load balancers""" + cmd = listLoadBalancers.listLoadBalancersCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listLoadBalancerRules(cmd)) \ No newline at end of file diff --git a/ui/scripts/projects.js b/ui/scripts/projects.js index 4004709a75d..ea1e6dbdd02 100644 --- a/ui/scripts/projects.js +++ b/ui/scripts/projects.js @@ -849,6 +849,9 @@ getUpdatedItem: function(data) { return $.extend(data, { state: 'Destroyed' }); }, + onComplete: function(data) { + $(window).trigger('cloudStack.deleteProject', args); + }, getActionFilter: function(args) { return function() { return []; diff --git a/ui/scripts/ui-custom/projects.js b/ui/scripts/ui-custom/projects.js index 82abadd7fe8..7824b3de11c 100644 --- a/ui/scripts/ui-custom/projects.js +++ b/ui/scripts/ui-custom/projects.js @@ -333,9 +333,15 @@ response: { success: function(args) { var project = args.data; + var $projectSwitcher = $('div.project-switcher'); $(window).trigger('cloudStack.fullRefresh'); + // dynamically add newly created project into project switcher + $projectSwitcher.find('select').append( + $('