From bd8976010835acaa690c85775d0504a4ff8df894 Mon Sep 17 00:00:00 2001 From: Nicolas Vazquez Date: Wed, 9 May 2018 13:03:11 -0300 Subject: [PATCH] config-drive: support user data on L2 networks (#2615) Supporting ConfigDrive user data on L2 networks. Add UI checkbox to create L2 network offering with config drive. Signed-off-by: Rohit Yadav --- .../orchestration/NetworkOrchestrator.java | 20 ++++++++- .../NetworkOrchestratorTest.java | 41 +++++++++++++++++++ .../com/cloud/network/NetworkModelImpl.java | 2 +- .../cloud/network/guru/GuestNetworkGuru.java | 4 +- ui/scripts/configuration.js | 19 +++++++++ ui/scripts/docs.js | 4 ++ 6 files changed, 85 insertions(+), 5 deletions(-) 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 1b707c3979d..5508472431a 100644 --- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -2291,13 +2291,14 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra 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); @@ -2396,6 +2397,21 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra 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 2d15403a865..b0283f35c1b 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.ArrayList; 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 @@ import junit.framework.TestCase; /** * 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 class NetworkOrchestratorTest extends TestCase { 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 class NetworkOrchestratorTest extends TestCase { 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 5edd2288231..60b21c382eb 100644 --- a/server/src/com/cloud/network/NetworkModelImpl.java +++ b/server/src/com/cloud/network/NetworkModelImpl.java @@ -580,7 +580,7 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi 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 c7e6aca22b8..3f6562e0eee 100644 --- a/server/src/com/cloud/network/guru/GuestNetworkGuru.java +++ b/server/src/com/cloud/network/guru/GuestNetworkGuru.java @@ -200,7 +200,7 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur 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 abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur 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 79916f5e411..2eee7bbeae0 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 583db58bb45..bbe8f3e64b4 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: '' } };