diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index cec2e5926c17..f9d63676f8c6 100644 --- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -2290,13 +2290,14 @@ public Network createGuestNetwork(final long networkOfferingId, final String nam final boolean cidrRequired = zone.getNetworkType() == NetworkType.Advanced && ntwkOff.getTrafficType() == TrafficType.Guest && (ntwkOff.getGuestType() == GuestType.Shared || (ntwkOff.getGuestType() == GuestType.Isolated - && !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat)) - || ntwkOff.getGuestType() == GuestType.L2 && !_networkModel.listNetworkOfferingServices(ntwkOff.getId()).isEmpty()); + && !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat))); if (cidr == null && ip6Cidr == null && cidrRequired) { throw new InvalidParameterValueException("StartIp/endIp/gateway/netmask are required when create network of" + " type " + Network.GuestType.Shared + " and network of type " + GuestType.Isolated + " with service " + Service.SourceNat.getName() + " disabled"); } + checkL2OfferingServices(ntwkOff); + // No cidr can be specified in Basic zone if (zone.getNetworkType() == NetworkType.Basic && cidr != null) { throw new InvalidParameterValueException("StartIp/endIp/gateway/netmask can't be specified for zone of type " + NetworkType.Basic); @@ -2395,6 +2396,21 @@ public Network doInTransaction(final TransactionStatus status) { return network; } + /** + * Checks for L2 network offering services. Only 2 cases allowed: + * - No services + * - User Data service only, provided by ConfigDrive + * @param ntwkOff network offering + */ + protected void checkL2OfferingServices(NetworkOfferingVO ntwkOff) { + if (ntwkOff.getGuestType() == GuestType.L2 && !_networkModel.listNetworkOfferingServices(ntwkOff.getId()).isEmpty() && + (!_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.UserData) || + (_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.UserData) && + _networkModel.listNetworkOfferingServices(ntwkOff.getId()).size() > 1))) { + throw new InvalidParameterValueException("For L2 networks, only UserData service is allowed"); + } + } + @Override @DB public boolean shutdownNetwork(final long networkId, final ReservationContext context, final boolean cleanupElements) { diff --git a/engine/orchestration/test/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java b/engine/orchestration/test/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java index 2d15403a8657..b0283f35c1b3 100644 --- a/engine/orchestration/test/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java +++ b/engine/orchestration/test/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java @@ -26,10 +26,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Arrays; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.offerings.NetworkOfferingVO; import org.apache.log4j.Logger; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; import org.mockito.Matchers; import com.cloud.network.Network; @@ -56,6 +61,7 @@ /** * NetworkManagerImpl implements NetworkManager. */ +@RunWith(JUnit4.class) public class NetworkOrchestratorTest extends TestCase { static final Logger s_logger = Logger.getLogger(NetworkOrchestratorTest.class); @@ -65,6 +71,10 @@ public class NetworkOrchestratorTest extends TestCase { String dhcpProvider = "VirtualRouter"; NetworkGuru guru = mock(NetworkGuru.class); + NetworkOfferingVO networkOffering = mock(NetworkOfferingVO.class); + + private static final long networkOfferingId = 1l; + @Override @Before public void setUp() { @@ -90,6 +100,9 @@ public void setUp() { List networkGurus = new ArrayList(); networkGurus.add(guru); testOrchastrator.networkGurus = networkGurus; + + when(networkOffering.getGuestType()).thenReturn(GuestType.L2); + when(networkOffering.getId()).thenReturn(networkOfferingId); } @Test @@ -159,4 +172,32 @@ public void testDontRemoveDhcpServiceWhenNotProvided() { verify(testOrchastrator._ntwkSrvcDao, never()).getProviderForServiceInNetwork(network.getId(), Service.Dhcp); verify(testOrchastrator._networksDao, times(1)).findById(nic.getNetworkId()); } + + @Test + public void testCheckL2OfferingServicesEmptyServices() { + when(testOrchastrator._networkModel.listNetworkOfferingServices(networkOfferingId)).thenReturn(new ArrayList<>()); + when(testOrchastrator._networkModel.areServicesSupportedByNetworkOffering(networkOfferingId, Service.UserData)).thenReturn(false); + testOrchastrator.checkL2OfferingServices(networkOffering); + } + + @Test + public void testCheckL2OfferingServicesUserDataOnly() { + when(testOrchastrator._networkModel.listNetworkOfferingServices(networkOfferingId)).thenReturn(Arrays.asList(Service.UserData)); + when(testOrchastrator._networkModel.areServicesSupportedByNetworkOffering(networkOfferingId, Service.UserData)).thenReturn(true); + testOrchastrator.checkL2OfferingServices(networkOffering); + } + + @Test(expected = InvalidParameterValueException.class) + public void testCheckL2OfferingServicesMultipleServicesIncludingUserData() { + when(testOrchastrator._networkModel.listNetworkOfferingServices(networkOfferingId)).thenReturn(Arrays.asList(Service.UserData, Service.Dhcp)); + when(testOrchastrator._networkModel.areServicesSupportedByNetworkOffering(networkOfferingId, Service.UserData)).thenReturn(true); + testOrchastrator.checkL2OfferingServices(networkOffering); + } + + @Test(expected = InvalidParameterValueException.class) + public void testCheckL2OfferingServicesMultipleServicesNotIncludingUserData() { + when(testOrchastrator._networkModel.listNetworkOfferingServices(networkOfferingId)).thenReturn(Arrays.asList(Service.Dns, Service.Dhcp)); + when(testOrchastrator._networkModel.areServicesSupportedByNetworkOffering(networkOfferingId, Service.UserData)).thenReturn(false); + testOrchastrator.checkL2OfferingServices(networkOffering); + } } diff --git a/server/src/com/cloud/network/NetworkModelImpl.java b/server/src/com/cloud/network/NetworkModelImpl.java index 5edd2288231c..60b21c382ebd 100644 --- a/server/src/com/cloud/network/NetworkModelImpl.java +++ b/server/src/com/cloud/network/NetworkModelImpl.java @@ -580,7 +580,7 @@ public boolean canUseForDeploy(Network network) { if (network.getTrafficType() != TrafficType.Guest) { return false; } - if (listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty()) { + if (network.getGuestType() == GuestType.L2 || listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty()) { return true; // do not check free IPs if there is no service in the network } boolean hasFreeIps = true; diff --git a/server/src/com/cloud/network/guru/GuestNetworkGuru.java b/server/src/com/cloud/network/guru/GuestNetworkGuru.java index c7e6aca22b8f..3f6562e0eee1 100644 --- a/server/src/com/cloud/network/guru/GuestNetworkGuru.java +++ b/server/src/com/cloud/network/guru/GuestNetworkGuru.java @@ -200,7 +200,7 @@ public Network design(final NetworkOffering offering, final DeploymentPlan plan, if (userSpecified.getCidr() != null) { network.setCidr(userSpecified.getCidr()); network.setGateway(userSpecified.getGateway()); - } else if (offering.getGuestType() == GuestType.Shared || !_networkModel.listNetworkOfferingServices(offering.getId()).isEmpty()) { + } else if (offering.getGuestType() != GuestType.L2 && (offering.getGuestType() == GuestType.Shared || !_networkModel.listNetworkOfferingServices(offering.getId()).isEmpty())) { final String guestNetworkCidr = dc.getGuestNetworkCidr(); if (guestNetworkCidr != null) { final String[] cidrTuple = guestNetworkCidr.split("\\/"); @@ -370,7 +370,7 @@ public NicProfile allocate(final Network network, NicProfile nic, final VirtualM guestIp = network.getGateway(); } else { guestIp = _ipAddrMgr.acquireGuestIpAddress(network, nic.getRequestedIPv4()); - if (guestIp == null && !_networkModel.listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty()) { + if (guestIp == null && network.getGuestType() != GuestType.L2 && !_networkModel.listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty()) { throw new InsufficientVirtualNetworkCapacityException("Unable to acquire Guest IP" + " address for network " + network, DataCenter.class, dc.getId()); } diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 79916f5e411e..2eee7bbeae0f 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -2404,8 +2404,10 @@ var $useVpc = args.$form.find('.form-item[rel=\"useVpc\"]'); var $useVpcCb = $useVpc.find("input[type=checkbox]"); var $supportedServices = args.$form.find('.form-item[rel=\"supportedServices\"]'); + var $userDataL2 = args.$form.find('.form-item[rel=\"userDataL2\"]'); if ($guestTypeField.val() == 'Shared') { //Shared network offering $useVpc.hide(); + $userDataL2.hide(); $supportedServices.css('display', 'inline-block'); if ($useVpcCb.is(':checked')) { //if useVpc is checked, $useVpcCb.removeAttr("checked"); //remove "checked" attribute in useVpc @@ -2413,9 +2415,11 @@ } else if ($guestTypeField.val() == 'Isolated') { //Isolated network offering $useVpc.css('display', 'inline-block'); $supportedServices.css('display', 'inline-block'); + $userDataL2.hide(); } else if ($guestTypeField.val() == 'L2') { $useVpc.hide(); $supportedServices.hide(); + $userDataL2.css('display', 'inline-block'); } var $providers = $useVpcCb.closest('form').find('.dynamic-input select[name!="service.Connectivity.provider"]'); var $optionsOfProviders = $providers.find('option'); @@ -2803,6 +2807,13 @@ isBoolean: true }, + userDataL2: { + label: 'label.user.data', + docID: 'helpL2UserData', + isBoolean: true, + isHidden: true + }, + lbType: { //only shown when VPC is checked and LB service is checked label: 'label.load.balancer.type', isHidden: true, @@ -3384,6 +3395,14 @@ } else { //specifyVlan checkbox is unchecked delete inputData.specifyVlan; //if specifyVlan checkbox is unchecked, do not pass specifyVlan parameter to API call since we need to keep API call's size as small as possible (p.s. specifyVlan is defaulted as false at server-side) } + + if (inputData['userDataL2'] == 'on') { + inputData['serviceProviderList[0].service'] = 'UserData'; + inputData['serviceProviderList[0].provider'] = 'ConfigDrive'; + inputData['supportedServices'] = 'UserData'; + } else { + delete inputData.serviceProviderList; + } } if (inputData['forvpc'] == 'on') { diff --git a/ui/scripts/docs.js b/ui/scripts/docs.js index 583db58bb451..bbe8f3e64b49 100755 --- a/ui/scripts/docs.js +++ b/ui/scripts/docs.js @@ -1351,5 +1351,9 @@ cloudStack.docs = { }, helpSetReservationSystemVms: { desc: 'If enabled, IP range reservation is set for SSVM & CPVM. Global setting "system.vm.public.ip.reservation.mode.strictness" is used to control whether reservation is strict or not (preferred)' + }, + helpL2UserData: { + desc: 'Pass user and meta data to VMs (via ConfigDrive)', + externalLink: '' } };