From d765283e209199bf0925a64d50dff67fc7410967 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Mon, 30 Apr 2018 07:55:03 -0300 Subject: [PATCH 1/6] config-drive: Supporting ConfigDrive user data on L2 networks Signed-off-by: Rohit Yadav --- .../cloudstack/engine/orchestration/NetworkOrchestrator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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..7f6e20428aa4 100644 --- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -2291,7 +2291,9 @@ public Network createGuestNetwork(final long networkOfferingId, final String nam && 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()); + || ntwkOff.getGuestType() == GuestType.L2 + && !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.UserData) + && !_networkModel.listNetworkOfferingServices(ntwkOff.getId()).isEmpty()); 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"); From 33f42271ae20b41d4c6736499fddcc574d4920ea Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 4 May 2018 15:03:27 +0530 Subject: [PATCH 2/6] ui: allow support services to be configured for L2 This would allow configuring of config drive while creating a L2 network offering. Signed-off-by: Rohit Yadav --- ui/scripts/configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 79916f5e411e..850219ba27f9 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -2415,7 +2415,7 @@ $supportedServices.css('display', 'inline-block'); } else if ($guestTypeField.val() == 'L2') { $useVpc.hide(); - $supportedServices.hide(); + $supportedServices.css('display', 'inline-block'); } var $providers = $useVpcCb.closest('form').find('.dynamic-input select[name!="service.Connectivity.provider"]'); var $optionsOfProviders = $providers.find('option'); From 764381d28fe31804dd296c2478718f09663e6262 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 4 May 2018 15:39:57 +0530 Subject: [PATCH 3/6] networkguru: don't force CIDRs on L2 guest network Signed-off-by: Rohit Yadav --- server/src/com/cloud/network/guru/GuestNetworkGuru.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/com/cloud/network/guru/GuestNetworkGuru.java b/server/src/com/cloud/network/guru/GuestNetworkGuru.java index c7e6aca22b8f..76b25c57bb66 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("\\/"); From d5f2e8c47fb3b98d02fb1deae1fa04e1f6a816a0 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 4 May 2018 15:53:14 +0530 Subject: [PATCH 4/6] network: allow L2 to be used for deployment Signed-off-by: Rohit Yadav --- server/src/com/cloud/network/NetworkModelImpl.java | 2 +- server/src/com/cloud/network/guru/GuestNetworkGuru.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 76b25c57bb66..3f6562e0eee1 100644 --- a/server/src/com/cloud/network/guru/GuestNetworkGuru.java +++ b/server/src/com/cloud/network/guru/GuestNetworkGuru.java @@ -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()); } From bfef680fe26e48d7a2736c6f8c7dd3b2a7a0312b Mon Sep 17 00:00:00 2001 From: nvazquez Date: Sat, 5 May 2018 17:00:48 -0300 Subject: [PATCH 5/6] UI improvement and checks for services --- .../orchestration/NetworkOrchestrator.java | 22 ++++++++-- .../NetworkOrchestratorTest.java | 41 +++++++++++++++++++ ui/scripts/configuration.js | 21 +++++++++- ui/scripts/docs.js | 4 ++ 4 files changed, 83 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 7f6e20428aa4..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,15 +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.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.UserData) - && !_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); @@ -2397,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/ui/scripts/configuration.js b/ui/scripts/configuration.js index 850219ba27f9..e08719a3ab76 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.css('display', 'inline-block'); + $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, @@ -3406,6 +3417,14 @@ serviceProviderIndex++; }); + if (inputData['userDataL2'] == 'on') { + inputData['serviceProviderList[0].service'] = 'UserData'; + inputData['serviceProviderList[0].provider'] = 'ConfigDrive'; + inputData['supportedServices'] = 'UserData'; + } else { + delete inputData.serviceProviderList; + } + if (args.$form.find('.form-item[rel=egressdefaultpolicy]').is(':visible')) { if (formData.egressdefaultpolicy === 'ALLOW') { delete inputData.egressdefaultpolicy; //do not pass egressdefaultpolicy to API call since we need to keep API call's size as small as possible (p.s. egressdefaultpolicy is defaulted as true at server-side) 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: '' } }; From 734915c71da6697cf05e22d186c7ebfb92905579 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Tue, 8 May 2018 09:27:12 -0300 Subject: [PATCH 6/6] Check for new checkbox only on L2 --- ui/scripts/configuration.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index e08719a3ab76..2eee7bbeae0f 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -3395,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') { @@ -3417,14 +3425,6 @@ serviceProviderIndex++; }); - if (inputData['userDataL2'] == 'on') { - inputData['serviceProviderList[0].service'] = 'UserData'; - inputData['serviceProviderList[0].provider'] = 'ConfigDrive'; - inputData['supportedServices'] = 'UserData'; - } else { - delete inputData.serviceProviderList; - } - if (args.$form.find('.form-item[rel=egressdefaultpolicy]').is(':visible')) { if (formData.egressdefaultpolicy === 'ALLOW') { delete inputData.egressdefaultpolicy; //do not pass egressdefaultpolicy to API call since we need to keep API call's size as small as possible (p.s. egressdefaultpolicy is defaulted as true at server-side)