[ https://issues.apache.org/jira/browse/CLOUDSTACK-10328?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16610758#comment-16610758 ]
ASF GitHub Bot commented on CLOUDSTACK-10328: --------------------------------------------- rafaelweingartner closed pull request #2773: CLOUDSTACK-10328: Add Secondary IPv6 address through API URL: https://github.com/apache/cloudstack/pull/2773 This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/api/src/main/java/com/cloud/network/NetworkModel.java b/api/src/main/java/com/cloud/network/NetworkModel.java index a5bf5e44610..002271ab854 100644 --- a/api/src/main/java/com/cloud/network/NetworkModel.java +++ b/api/src/main/java/com/cloud/network/NetworkModel.java @@ -282,7 +282,7 @@ boolean isNetworkInlineMode(Network network); - boolean isIP6AddressAvailableInNetwork(long networkId); + boolean areThereIPv6AddressAvailableInNetwork(long networkId); boolean isIP6AddressAvailableInVlan(long vlanId); diff --git a/api/src/main/java/com/cloud/network/NetworkService.java b/api/src/main/java/com/cloud/network/NetworkService.java index d76d6597202..aa33defba93 100644 --- a/api/src/main/java/com/cloud/network/NetworkService.java +++ b/api/src/main/java/com/cloud/network/NetworkService.java @@ -34,6 +34,7 @@ import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network.IpAddresses; import com.cloud.network.Network.Service; import com.cloud.network.Networks.TrafficType; import com.cloud.network.vpc.Vpc; @@ -155,15 +156,6 @@ PhysicalNetworkTrafficType addTrafficTypeToPhysicalNetwork(Long physicalNetworkI List<? extends Network> getIsolatedNetworksWithSourceNATOwnedByAccountInZone(long zoneId, Account owner); - /** - * @param networkId - * @param entityId - * @return - * @throws ConcurrentOperationException - * @throws ResourceUnavailableException - * @throws ResourceAllocationException - * @throws InsufficientAddressCapacityException - */ IpAddress associateIPToNetwork(long ipId, long networkId) throws InsufficientAddressCapacityException, ResourceAllocationException, ResourceUnavailableException, ConcurrentOperationException; @@ -189,12 +181,16 @@ Network createPrivateNetwork(String networkName, String displayText, long physic String netmask, long networkOwnerId, Long vpcId, Boolean sourceNat, Long networkOfferingId) throws ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException; - /* Requests an IP address for the guest nic */ - NicSecondaryIp allocateSecondaryGuestIP(long nicId, String ipaddress) throws InsufficientAddressCapacityException; + /** + * Requests an IP address for the guest NIC + */ + NicSecondaryIp allocateSecondaryGuestIP(long nicId, IpAddresses requestedIpPair) throws InsufficientAddressCapacityException; boolean releaseSecondaryIpFromNic(long ipAddressId); - /* lists the nic informaton */ + /** + * lists the NIC information + */ List<? extends Nic> listNics(ListNicsCmd listNicsCmd); Map<Network.Capability, String> getNetworkOfferingServiceCapabilities(NetworkOffering offering, Service service); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddIpToVmNicCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddIpToVmNicCmd.java index 009c4fd2fdf..ba465ad4259 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddIpToVmNicCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddIpToVmNicCmd.java @@ -29,7 +29,6 @@ import org.apache.log4j.Logger; import com.cloud.dc.DataCenter; -import com.cloud.dc.DataCenter.NetworkType; import com.cloud.event.EventTypes; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientAddressCapacityException; @@ -38,6 +37,7 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.Network; +import com.cloud.network.Network.IpAddresses; import com.cloud.utils.net.NetUtils; import com.cloud.vm.Nic; import com.cloud.vm.NicSecondaryIp; @@ -78,20 +78,6 @@ public long getNicId() { return nicId; } - private String getIpaddress() { - if (ipAddr != null) { - return ipAddr; - } else { - return null; - } - } - - private NetworkType getNetworkType() { - Network ntwk = _entityMgr.findById(Network.class, getNetworkId()); - DataCenter dc = _entityMgr.findById(DataCenter.class, ntwk.getDataCenterId()); - return dc.getNetworkType(); - } - private boolean isZoneSGEnabled() { Network ntwk = _entityMgr.findById(Network.class, getNetworkId()); DataCenter dc = _entityMgr.findById(DataCenter.class, ntwk.getDataCenterId()); @@ -144,7 +130,6 @@ public void execute() throws ResourceUnavailableException, ResourceAllocationExc } } - @Override public Long getSyncObjId() { return getNetworkId(); @@ -169,17 +154,15 @@ public long getEntityOwnerId() { @Override public void create() throws ResourceAllocationException { - String ip; NicSecondaryIp result; - String secondaryIp = null; - if ((ip = getIpaddress()) != null) { - if (!NetUtils.isValidIp4(ip)) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Invalid ip address " + ip); - } + + IpAddresses requestedIpPair = new IpAddresses(ipAddr, null); + if (!NetUtils.isIpv4(ipAddr)) { + requestedIpPair = new IpAddresses(null, ipAddr); } try { - result = _networkService.allocateSecondaryGuestIP(getNicId(), getIpaddress()); + result = _networkService.allocateSecondaryGuestIP(getNicId(), requestedIpPair); if (result != null) { setEntityId(result.getId()); setEntityUuid(result.getUuid()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveIpFromVmNicCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveIpFromVmNicCmd.java index 19da39a5970..db84dc9fc8b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveIpFromVmNicCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveIpFromVmNicCmd.java @@ -147,10 +147,15 @@ public void execute() throws InvalidParameterValueException { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Invalid IP id is passed"); } + String secIp = nicSecIp.getIp4Address(); + if (secIp == null) { + secIp = nicSecIp.getIp6Address(); + } + if (isZoneSGEnabled()) { //remove the security group rules for this secondary ip boolean success = false; - success = _securityGroupService.securityGroupRulesForVmSecIp(nicSecIp.getNicId(), nicSecIp.getIp4Address(), false); + success = _securityGroupService.securityGroupRulesForVmSecIp(nicSecIp.getNicId(), secIp, false); if (success == false) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to set security group rules for the secondary ip"); } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/AddIpToVmNicTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/AddIpToVmNicTest.java index 41395432578..8a283055448 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/test/AddIpToVmNicTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/AddIpToVmNicTest.java @@ -61,7 +61,7 @@ public void testCreateSuccess() throws ResourceAllocationException, ResourceUnav NicSecondaryIp secIp = Mockito.mock(NicSecondaryIp.class); Mockito.when( - networkService.allocateSecondaryGuestIP(Matchers.anyLong(), Matchers.anyString())) + networkService.allocateSecondaryGuestIP(Matchers.anyLong(), Matchers.any())) .thenReturn(secIp); ipTonicCmd._networkService = networkService; @@ -81,7 +81,7 @@ public void testCreateFailure() throws ResourceAllocationException, ResourceUnav AddIpToVmNicCmd ipTonicCmd = Mockito.mock(AddIpToVmNicCmd.class); Mockito.when( - networkService.allocateSecondaryGuestIP(Matchers.anyLong(), Matchers.anyString())) + networkService.allocateSecondaryGuestIP(Matchers.anyLong(), Matchers.any())) .thenReturn(null); ipTonicCmd._networkService = networkService; diff --git a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java index 256f0266635..a5512786bcd 100644 --- a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java @@ -166,18 +166,18 @@ void transferPortableIP(long ipAddrId, long currentNetworkId, long newNetworkId) * @throws ConcurrentOperationException * @throws InsufficientAddressCapacityException */ - PublicIp assignDedicateIpAddress(Account owner, Long guestNtwkId, Long vpcId, long dcId, boolean isSourceNat) throws ConcurrentOperationException, - InsufficientAddressCapacityException; + PublicIp assignDedicateIpAddress(Account owner, Long guestNtwkId, Long vpcId, long dcId, boolean isSourceNat) + throws ConcurrentOperationException, InsufficientAddressCapacityException; - IpAddress allocateIp(Account ipOwner, boolean isSystem, Account caller, long callerId, DataCenter zone, Boolean displayIp) throws ConcurrentOperationException, - ResourceAllocationException, InsufficientAddressCapacityException; + IpAddress allocateIp(Account ipOwner, boolean isSystem, Account caller, long callerId, DataCenter zone, Boolean displayIp) + throws ConcurrentOperationException, ResourceAllocationException, InsufficientAddressCapacityException; - PublicIp assignPublicIpAddressFromVlans(long dcId, Long podId, Account owner, VlanType type, List<Long> vlanDbIds, Long networkId, String requestedIp, - boolean isSystem) throws InsufficientAddressCapacityException; + PublicIp assignPublicIpAddressFromVlans(long dcId, Long podId, Account owner, VlanType type, List<Long> vlanDbIds, Long networkId, String requestedIp, boolean isSystem) + throws InsufficientAddressCapacityException; @DB - void allocateNicValues(NicProfile nic, DataCenter dc, VirtualMachineProfile vm, Network network, String requestedIpv4, - String requestedIpv6) throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException; + void allocateNicValues(NicProfile nic, DataCenter dc, VirtualMachineProfile vm, Network network, String requestedIpv4, String requestedIpv6) + throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException; int getRuleCountForIp(Long addressId, FirewallRule.Purpose purpose, FirewallRule.State state); @@ -187,6 +187,8 @@ void allocateNicValues(NicProfile nic, DataCenter dc, VirtualMachineProfile vm, AcquirePodIpCmdResponse allocatePodIp(String zoneId, String podId) throws ConcurrentOperationException, ResourceAllocationException; + public boolean isIpEqualsGatewayOrNetworkOfferingsEmpty(Network network, String requestedIp); + void releasePodIp(Long id) throws CloudRuntimeException; } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasDaoImpl.java index d1453aa4630..887b3d73087 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasDaoImpl.java @@ -113,6 +113,7 @@ protected NicIpAliasDaoImpl() { List<String> ips = new ArrayList<String>(results.size()); for (NicIpAliasVO result : results) { ips.add(result.getIp4Address()); + ips.add(result.getIp6Address()); } return ips; } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDao.java index 96b80b84dd7..cbb52e57282 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDao.java @@ -34,11 +34,7 @@ NicSecondaryIpVO findByIp4AddressAndNetworkId(String ip4Address, long networkId); - /** - * @param networkId - * @param instanceId - * @return - */ + NicSecondaryIpVO findByIp6AddressAndNetworkId(String ip6Address, long networkId); List<NicSecondaryIpVO> getSecondaryIpAddressesForVm(long vmId); diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDaoImpl.java index 01f53bc99fb..ba3a5c7917a 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpDaoImpl.java @@ -19,10 +19,9 @@ import java.util.ArrayList; import java.util.List; - -import com.cloud.utils.StringUtils; import org.springframework.stereotype.Component; +import com.cloud.utils.StringUtils; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.SearchBuilder; @@ -32,16 +31,18 @@ @Component public class NicSecondaryIpDaoImpl extends GenericDaoBase<NicSecondaryIpVO, Long> implements NicSecondaryIpDao { + private final SearchBuilder<NicSecondaryIpVO> AllFieldsSearch; private final GenericSearchBuilder<NicSecondaryIpVO, String> IpSearch; protected GenericSearchBuilder<NicSecondaryIpVO, Long> CountByNicId; - protected NicSecondaryIpDaoImpl() { + public NicSecondaryIpDaoImpl() { super(); AllFieldsSearch = createSearchBuilder(); AllFieldsSearch.and("instanceId", AllFieldsSearch.entity().getVmId(), Op.EQ); AllFieldsSearch.and("network", AllFieldsSearch.entity().getNetworkId(), Op.EQ); AllFieldsSearch.and("address", AllFieldsSearch.entity().getIp4Address(), Op.LIKE); + AllFieldsSearch.and("ip6address", AllFieldsSearch.entity().getIp6Address(), Op.LIKE); AllFieldsSearch.and("nicId", AllFieldsSearch.entity().getNicId(), Op.EQ); AllFieldsSearch.done(); @@ -132,6 +133,14 @@ public NicSecondaryIpVO findByIp4AddressAndNetworkId(String ip4Address, long net return findOneBy(sc); } + @Override + public NicSecondaryIpVO findByIp6AddressAndNetworkId(String ip6Address, long networkId) { + SearchCriteria<NicSecondaryIpVO> sc = AllFieldsSearch.create(); + sc.setParameters("network", networkId); + sc.setParameters("ip6address", ip6Address); + return findOneBy(sc); + } + @Override public NicSecondaryIpVO findByIp4AddressAndNicId(String ip4Address, long nicId) { SearchCriteria<NicSecondaryIpVO> sc = AllFieldsSearch.create(); diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index ba3d5c3e143..1bf1f45f169 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -471,7 +471,7 @@ public ConfigurationResponse createConfigurationResponse(Configuration cfg) { cfgResponse.setCategory(cfg.getCategory()); cfgResponse.setDescription(cfg.getDescription()); cfgResponse.setName(cfg.getName()); - if(cfg.isEncrypted()) { + if (cfg.isEncrypted()) { cfgResponse.setValue(DBEncryptionUtil.encrypt(cfg.getValue())); } else { cfgResponse.setValue(cfg.getValue()); @@ -3623,13 +3623,24 @@ public NicSecondaryIpResponse createSecondaryIPToNicResponse(NicSecondaryIp resu NicVO nic = _entityMgr.findById(NicVO.class, result.getNicId()); NetworkVO network = _entityMgr.findById(NetworkVO.class, result.getNetworkId()); response.setId(result.getUuid()); - response.setIpAddr(result.getIp4Address()); + setResponseIpAddress(result, response); response.setNicId(nic.getUuid()); response.setNwId(network.getUuid()); response.setObjectName("nicsecondaryip"); return response; } + /** + * Set the NicSecondaryIpResponse object with the IP address that is not null (IPv4 or IPv6) + */ + public static void setResponseIpAddress(NicSecondaryIp result, NicSecondaryIpResponse response) { + if (result.getIp4Address() != null) { + response.setIpAddr(result.getIp4Address()); + } else if (result.getIp6Address() != null) { + response.setIpAddr(result.getIp6Address()); + } + } + /** * The resulting Response attempts to be in line with what is returned from * @see com.cloud.api.query.dao.UserVmJoinDaoImpl#setUserVmResponse(ResponseView, UserVmResponse, UserVmJoinVO) @@ -3706,7 +3717,7 @@ public NicResponse createNicResponse(Nic result) { for (NicSecondaryIpVO ip : secondaryIps) { NicSecondaryIpResponse ipRes = new NicSecondaryIpResponse(); ipRes.setId(ip.getUuid()); - ipRes.setIpAddr(ip.getIp4Address()); + setResponseIpAddress(ip, ipRes); ipList.add(ipRes); } response.setSecondaryIps(ipList); diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index 00ec61ad846..58d5e493d6d 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -41,6 +41,7 @@ import org.springframework.stereotype.Component; import com.cloud.api.ApiDBUtils; +import com.cloud.api.ApiResponseHelper; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.gpu.GPU; import com.cloud.service.ServiceOfferingDetailsVO; @@ -267,15 +268,14 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us for (NicSecondaryIpVO ip : secondaryIps) { NicSecondaryIpResponse ipRes = new NicSecondaryIpResponse(); ipRes.setId(ip.getUuid()); - ipRes.setIpAddr(ip.getIp4Address()); + ApiResponseHelper.setResponseIpAddress(ip, ipRes); ipList.add(ipRes); } nicResponse.setSecondaryIps(ipList); } nicResponse.setObjectName("nic"); - List<NicExtraDhcpOptionResponse> nicExtraDhcpOptionResponses = _nicExtraDhcpOptionDao.listByNicId(nic_id) - .stream() + List<NicExtraDhcpOptionResponse> nicExtraDhcpOptionResponses = _nicExtraDhcpOptionDao.listByNicId(nic_id).stream() .map(vo -> new NicExtraDhcpOptionResponse(Dhcp.DhcpOptionCode.valueOfInt(vo.getCode()).getName(), vo.getCode(), vo.getValue())) .collect(Collectors.toList()); nicResponse.setExtraDhcpOptions(nicExtraDhcpOptionResponses); @@ -400,7 +400,7 @@ public UserVmResponse setUserVmResponse(ResponseView view, UserVmResponse userVm for (NicSecondaryIpVO ip : secondaryIps) { NicSecondaryIpResponse ipRes = new NicSecondaryIpResponse(); ipRes.setId(ip.getUuid()); - ipRes.setIpAddr(ip.getIp4Address()); + ApiResponseHelper.setResponseIpAddress(ip, ipRes); ipList.add(ipRes); } nicResponse.setSecondaryIps(ipList); diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java index 71acc834d9a..ec7adfd1057 100644 --- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java @@ -179,25 +179,25 @@ private static final Logger s_logger = Logger.getLogger(IpAddressManagerImpl.class); @Inject - NetworkOrchestrationService _networkMgr = null; + NetworkOrchestrationService _networkMgr; @Inject - EntityManager _entityMgr = null; + EntityManager _entityMgr; @Inject - DataCenterDao _dcDao = null; + DataCenterDao _dcDao; @Inject - VlanDao _vlanDao = null; + VlanDao _vlanDao; @Inject - IPAddressDao _ipAddressDao = null; + IPAddressDao _ipAddressDao; @Inject - AccountDao _accountDao = null; + AccountDao _accountDao; @Inject - DomainDao _domainDao = null; + DomainDao _domainDao; @Inject - UserDao _userDao = null; + UserDao _userDao; @Inject ConfigurationDao _configDao; @Inject - UserVmDao _userVmDao = null; + UserVmDao _userVmDao; @Inject AlertManager _alertMgr; @Inject @@ -209,11 +209,11 @@ @Inject DomainVlanMapDao _domainVlanMapDao; @Inject - NetworkOfferingDao _networkOfferingDao = null; + NetworkOfferingDao _networkOfferingDao; @Inject - NetworkDao _networksDao = null; + NetworkDao _networksDao; @Inject - NicDao _nicDao = null; + NicDao _nicDao; @Inject RulesManager _rulesMgr; @Inject @@ -300,6 +300,8 @@ Boolean.class, "system.vm.public.ip.reservation.mode.strictness", "false", "If enabled, the use of System VMs public IP reservation is strict, preferred if not.", false, ConfigKey.Scope.Global); + private Random rand = new Random(System.currentTimeMillis()); + @Override public boolean configure(String name, Map<String, Object> params) { // populate providers @@ -1840,11 +1842,9 @@ public String acquireGuestIpAddress(Network network, String requestedIp) { return requestedIp; } - return NetUtils.long2Ip(array[_rand.nextInt(array.length)]); + return NetUtils.long2Ip(array[rand.nextInt(array.length)]); } - Random _rand = new Random(System.currentTimeMillis()); - /** * Get the list of public IPs that need to be applied for a static NAT enable/disable operation. * Manipulating only these ips prevents concurrency issues when disabling static nat at the same time. @@ -2184,4 +2184,18 @@ public String getConfigComponentName() { public ConfigKey<?>[] getConfigKeys() { return new ConfigKey<?>[] {UseSystemPublicIps, RulesContinueOnError, SystemVmPublicIpReservationModeStrictness}; } + + /** + * Returns true if the given IP address is equals the gateway or there is no network offerrings for the given network + */ + @Override + public boolean isIpEqualsGatewayOrNetworkOfferingsEmpty(Network network, String requestedIp) { + if (requestedIp.equals(network.getGateway()) || requestedIp.equals(network.getIp6Gateway())) { + return true; + } + if (_networkModel.listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty() && network.getCidr() == null) { + return true; + } + return false; + } } diff --git a/server/src/main/java/com/cloud/network/Ipv6AddressManager.java b/server/src/main/java/com/cloud/network/Ipv6AddressManager.java index 8a7049bb16e..4db3ec1c4c8 100644 --- a/server/src/main/java/com/cloud/network/Ipv6AddressManager.java +++ b/server/src/main/java/com/cloud/network/Ipv6AddressManager.java @@ -26,4 +26,11 @@ public UserIpv6Address assignDirectIp6Address(long dcId, Account owner, Long networkId, String requestedIp6) throws InsufficientAddressCapacityException; public void revokeDirectIpv6Address(long networkId, String ip6Address); + + public String allocateGuestIpv6(Network network, String requestedIpv6) throws InsufficientAddressCapacityException; + + public String allocatePublicIp6ForGuestNic(Network network, Long podId, Account ipOwner, String requestedIp) throws InsufficientAddressCapacityException; + + public String acquireGuestIpv6Address(Network network, String requestedIpv6) throws InsufficientAddressCapacityException; + } diff --git a/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java b/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java index 53fb25c5285..1cb432efdc0 100644 --- a/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java @@ -23,9 +23,8 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; -import org.apache.log4j.Logger; - import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.log4j.Logger; import com.cloud.configuration.Config; import com.cloud.dc.DataCenter; @@ -35,13 +34,21 @@ import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.VlanDao; import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.IpAddress.State; +import com.cloud.network.Network.IpAddresses; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.UserIpv6AddressDao; import com.cloud.user.Account; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.DB; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; +import com.cloud.vm.dao.NicSecondaryIpDao; +import com.cloud.vm.dao.NicSecondaryIpVO; public class Ipv6AddressManagerImpl extends ManagerBase implements Ipv6AddressManager { public static final Logger s_logger = Logger.getLogger(Ipv6AddressManagerImpl.class.getName()); @@ -61,6 +68,12 @@ NetworkDao _networkDao; @Inject ConfigurationDao _configDao; + @Inject + IpAddressManager ipAddressManager; + @Inject + NicSecondaryIpDao nicSecondaryIpDao; + @Inject + IPAddressDao ipAddressDao; @Override public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { @@ -84,7 +97,7 @@ public UserIpv6Address assignDirectIp6Address(long dcId, Account owner, Long net String ip = null; Vlan ipVlan = null; if (requestedIp6 == null) { - if (!_networkModel.isIP6AddressAvailableInNetwork(networkId)) { + if (!_networkModel.areThereIPv6AddressAvailableInNetwork(networkId)) { throw new InsufficientAddressCapacityException("There is no more address available in the network " + network.getName(), DataCenter.class, network.getDataCenterId()); } @@ -132,7 +145,7 @@ public UserIpv6Address assignDirectIp6Address(long dcId, Account owner, Long net dc.setMacAddress(nextMac); _dcDao.update(dc.getId(), dc); - String macAddress = NetUtils.long2Mac(NetUtils.createSequenceBasedMacAddress(mac,NetworkModel.MACIdentifier.value())); + String macAddress = NetUtils.long2Mac(NetUtils.createSequenceBasedMacAddress(mac, NetworkModel.MACIdentifier.value())); UserIpv6AddressVO ipVO = new UserIpv6AddressVO(ip, dcId, macAddress, ipVlan.getId()); ipVO.setPhysicalNetworkId(network.getPhysicalNetworkId()); ipVO.setSourceNetworkId(networkId); @@ -150,4 +163,101 @@ public void revokeDirectIpv6Address(long networkId, String ip6Address) { _ipv6Dao.remove(ip.getId()); } } + + /** + * Executes method {@link #acquireGuestIpv6Address(Network, String)} and returns the requested IPv6 (String) in case of successfully allocating the guest IPv6 address. + */ + @Override + public String allocateGuestIpv6(Network network, String requestedIpv6) throws InsufficientAddressCapacityException { + return acquireGuestIpv6Address(network, requestedIpv6); + } + + /** + * Allocates a guest IPv6 address for the guest NIC. It will throw exceptions in the following cases: + * <ul> + * <li>there is no IPv6 address available in the network;</li> + * <li>IPv6 address is equals to the Gateway;</li> + * <li>the network offering is empty;</li> + * <li>the IPv6 address is not in the network;</li> + * <li>the requested IPv6 address is already in use in the network.</li> + * </ul> + */ + @Override + @DB + public String acquireGuestIpv6Address(Network network, String requestedIpv6) throws InsufficientAddressCapacityException { + if (!_networkModel.areThereIPv6AddressAvailableInNetwork(network.getId())) { + throw new InsufficientAddressCapacityException( + String.format("There is no IPv6 address available in the network [name=%s, network id=%s]", network.getName(), network.getId()), DataCenter.class, + network.getDataCenterId()); + } + + checkIfCanAllocateIpv6Address(network, requestedIpv6); + + IpAddresses requestedIpPair = new IpAddresses(null, requestedIpv6); + _networkModel.checkRequestedIpAddresses(network.getId(), requestedIpPair); + + IPAddressVO ip = ipAddressDao.findByIpAndSourceNetworkId(network.getId(), requestedIpv6); + if (ip != null) { + State ipState = ip.getState(); + if (ipState != State.Free) { + throw new InsufficientAddressCapacityException(String.format("Requested ip address [%s] is not free [ip state=%]", requestedIpv6, ipState), DataCenter.class, + network.getDataCenterId()); + } + } + return requestedIpv6; + } + + /** + * Allocates a public IPv6 address for the guest NIC. It will throw exceptions in the following cases: + * <ul> + * <li>the the requested IPv6 address is already in use in the network;</li> + * <li>IPv6 address is equals to the Gateway;</li> + * <li>the network offering is empty;</li> + * <li>the IPv6 address is not in the network.</li> + * </ul> + */ + @Override + public String allocatePublicIp6ForGuestNic(Network network, Long podId, Account owner, String requestedIpv6) throws InsufficientAddressCapacityException { + checkIfCanAllocateIpv6Address(network, requestedIpv6); + + return requestedIpv6; + } + + /** + * Performs some checks on the given IPv6 address. It will throw exceptions in the following cases: + * <ul> + * <li>the the requested IPv6 address is already in use in the network;</li> + * <li>IPv6 address is equals to the Gateway;</li> + * <li>the network offering is empty;</li> + * <li>the IPv6 address is not in the network.</li> + * </ul> + */ + protected void checkIfCanAllocateIpv6Address(Network network, String ipv6) throws InsufficientAddressCapacityException { + if (isIp6Taken(network, ipv6)) { + throw new InsufficientAddressCapacityException( + String.format("The IPv6 address [%s] is already in use in the network [id=%s, name=%s]", ipv6, network.getId(), network.getName()), Network.class, + network.getId()); + } + + if (ipAddressManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, ipv6)) { + throw new InvalidParameterValueException( + String.format("The network [id=%s] offering is empty or the requested IP address [%s] is equals to the Gateway", network.getId(), ipv6)); + } + + String networkIp6Cidr = network.getIp6Cidr(); + if (!NetUtils.isIp6InNetwork(ipv6, networkIp6Cidr)) { + throw new InvalidParameterValueException( + String.format("The IPv6 address [%s] is not in the network [id=%s, name=%s, ipv6cidr=%s]", ipv6, network.getId(), network.getName(), network.getIp6Cidr())); + } + } + + /** + * Returns false if the requested ipv6 address is taken by some VM, checking on the 'user_ipv6_address' table or 'nic_secondary_ips' table. + */ + protected boolean isIp6Taken(Network network, String requestedIpv6) { + UserIpv6AddressVO ip6Vo = _ipv6Dao.findByNetworkIdAndIp(network.getId(), requestedIpv6); + NicSecondaryIpVO nicSecondaryIpVO = nicSecondaryIpDao.findByIp6AddressAndNetworkId(requestedIpv6, network.getId()); + return ip6Vo != null || nicSecondaryIpVO != null; + } + } diff --git a/server/src/main/java/com/cloud/network/NetworkModelImpl.java b/server/src/main/java/com/cloud/network/NetworkModelImpl.java index bb1a1815ad8..c23de72692f 100644 --- a/server/src/main/java/com/cloud/network/NetworkModelImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkModelImpl.java @@ -592,7 +592,7 @@ public boolean canUseForDeploy(Network network) { return false; } if (network.getIp6Gateway() != null) { - hasFreeIps = isIP6AddressAvailableInNetwork(network.getId()); + hasFreeIps = areThereIPv6AddressAvailableInNetwork(network.getId()); } } else { if (network.getCidr() == null) { @@ -606,7 +606,7 @@ public boolean canUseForDeploy(Network network) { } @Override - public boolean isIP6AddressAvailableInNetwork(long networkId) { + public boolean areThereIPv6AddressAvailableInNetwork(long networkId) { Network network = _networksDao.findById(networkId); if (network == null) { return false; diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index f1397a11cf7..a8932a8b184 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -34,11 +34,10 @@ import java.util.Map; import java.util.Set; import java.util.UUID; + import javax.inject.Inject; import javax.naming.ConfigurationException; -import org.apache.log4j.Logger; - import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.ApiConstants; @@ -58,6 +57,7 @@ import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.PublishScope; import org.apache.cloudstack.network.element.InternalLoadBalancerElementService; +import org.apache.log4j.Logger; import com.cloud.api.ApiDBUtils; import com.cloud.configuration.Config; @@ -91,6 +91,7 @@ import com.cloud.network.IpAddress.State; import com.cloud.network.Network.Capability; import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.IpAddresses; import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; import com.cloud.network.Networks.BroadcastDomainType; @@ -293,6 +294,8 @@ @Inject IpAddressManager _ipAddrMgr; @Inject + Ipv6AddressManager ipv6AddrMgr; + @Inject EntityManager _entityMgr; @Inject public SecurityGroupService _securityGroupService; @@ -642,13 +645,16 @@ protected NetworkServiceImpl() { } @Override - @ActionEvent(eventType = EventTypes.EVENT_NIC_SECONDARY_IP_CONFIGURE, eventDescription = "Configuring secondary ip " + - "rules", async = true) + @ActionEvent(eventType = EventTypes.EVENT_NIC_SECONDARY_IP_CONFIGURE, eventDescription = "Configuring secondary ip " + "rules", async = true) public boolean configureNicSecondaryIp(NicSecondaryIp secIp, boolean isZoneSgEnabled) { boolean success = false; + String secondaryIp = secIp.getIp4Address(); + if (secIp.getIp4Address() == null) { + secondaryIp = secIp.getIp6Address(); + } if (isZoneSgEnabled) { - success = _securityGroupService.securityGroupRulesForVmSecIp(secIp.getNicId(), secIp.getIp4Address(), true); + success = _securityGroupService.securityGroupRulesForVmSecIp(secIp.getNicId(), secondaryIp, true); s_logger.info("Associated ip address to NIC : " + secIp.getIp4Address()); } else { success = true; @@ -656,11 +662,16 @@ public boolean configureNicSecondaryIp(NicSecondaryIp secIp, boolean isZoneSgEna return success; } + /** + * It allocates a secondary IP alias on the NIC. It can be either an Ipv4 or an Ipv6 or even both, according to the the given IpAddresses object. + */ @Override @ActionEvent(eventType = EventTypes.EVENT_NIC_SECONDARY_IP_ASSIGN, eventDescription = "assigning secondary ip to nic", create = true) - public NicSecondaryIp allocateSecondaryGuestIP(final long nicId, String requestedIp) throws InsufficientAddressCapacityException { + public NicSecondaryIp allocateSecondaryGuestIP(final long nicId, IpAddresses requestedIpPair) throws InsufficientAddressCapacityException { Account caller = CallContext.current().getCallingAccount(); + String ipv4Address = requestedIpPair.getIp4Address(); + String ipv6Address = requestedIpPair.getIp6Address(); //check whether the nic belongs to user vm. final NicVO nicVO = _nicDao.findById(nicId); @@ -690,18 +701,23 @@ public NicSecondaryIp allocateSecondaryGuestIP(final long nicId, String requeste int maxAllowedIpsPerNic = NumbersUtil.parseInt(_configDao.getValue(Config.MaxNumberOfSecondaryIPsPerNIC.key()), 10); Long nicWiseIpCount = _nicSecondaryIpDao.countByNicId(nicId); - if(nicWiseIpCount.intValue() >= maxAllowedIpsPerNic) { - s_logger.error("Maximum Number of Ips \"vm.network.nic.max.secondary.ipaddresses = \"" + maxAllowedIpsPerNic + " per Nic has been crossed for the nic " + nicId + "."); + if (nicWiseIpCount.intValue() >= maxAllowedIpsPerNic) { + s_logger.error("Maximum Number of Ips \"vm.network.nic.max.secondary.ipaddresses = \"" + maxAllowedIpsPerNic + " per Nic has been crossed for the nic " + nicId + "."); throw new InsufficientAddressCapacityException("Maximum Number of Ips per Nic has been crossed.", Nic.class, nicId); } - s_logger.debug("Calling the ip allocation ..."); String ipaddr = null; + String ip6addr = null; //Isolated network can exist in Basic zone only, so no need to verify the zone type if (network.getGuestType() == Network.GuestType.Isolated) { try { - ipaddr = _ipAddrMgr.allocateGuestIP(network, requestedIp); + if (ipv4Address != null) { + ipaddr = _ipAddrMgr.allocateGuestIP(network, ipv4Address); + } + if (ipv6Address != null) { + ip6addr = ipv6AddrMgr.allocateGuestIpv6(network, ipv6Address); + } } catch (InsufficientAddressCapacityException e) { throw new InvalidParameterValueException("Allocating guest ip for nic failed"); } @@ -711,16 +727,21 @@ public NicSecondaryIp allocateSecondaryGuestIP(final long nicId, String requeste DataCenter dc = _dcDao.findById(network.getDataCenterId()); if (dc.getNetworkType() == NetworkType.Basic) { - VMInstanceVO vmi = (VMInstanceVO)vm; + VMInstanceVO vmi = (VMInstanceVO)vm; podId = vmi.getPodIdToDeployIn(); - if (podId == null) { + if (podId == null) { throw new InvalidParameterValueException("vm pod id is null in Basic zone; can't decide the range for ip allocation"); - } + } } try { - ipaddr = _ipAddrMgr.allocatePublicIpForGuestNic(network, podId, ipOwner, requestedIp); - if (ipaddr == null) { + if (ipv4Address != null) { + ipaddr = _ipAddrMgr.allocatePublicIpForGuestNic(network, podId, ipOwner, ipv4Address); + } + if (ipv6Address != null) { + ip6addr = ipv6AddrMgr.allocatePublicIp6ForGuestNic(network, podId, ipOwner, ipv6Address); + } + if (ipaddr == null && ipv6Address == null) { throw new InvalidParameterValueException("Allocating ip to guest nic " + nicId + " failed"); } } catch (InsufficientAddressCapacityException e) { @@ -732,29 +753,30 @@ public NicSecondaryIp allocateSecondaryGuestIP(final long nicId, String requeste return null; } - if (ipaddr != null) { + if (ipaddr != null || ip6addr != null) { // we got the ip addr so up the nics table and secodary ip - final String addrFinal = ipaddr; + final String ip4AddrFinal = ipaddr; + final String ip6AddrFinal = ip6addr; long id = Transaction.execute(new TransactionCallback<Long>() { @Override public Long doInTransaction(TransactionStatus status) { - boolean nicSecondaryIpSet = nicVO.getSecondaryIp(); - if (!nicSecondaryIpSet) { - nicVO.setSecondaryIp(true); - // commit when previously set ?? - s_logger.debug("Setting nics table ..."); - _nicDao.update(nicId, nicVO); - } + boolean nicSecondaryIpSet = nicVO.getSecondaryIp(); + if (!nicSecondaryIpSet) { + nicVO.setSecondaryIp(true); + // commit when previously set ?? + s_logger.debug("Setting nics table ..."); + _nicDao.update(nicId, nicVO); + } - s_logger.debug("Setting nic_secondary_ip table ..."); + s_logger.debug("Setting nic_secondary_ip table ..."); Long vmId = nicVO.getInstanceId(); - NicSecondaryIpVO secondaryIpVO = new NicSecondaryIpVO(nicId, addrFinal, vmId, ipOwner.getId(), ipOwner.getDomainId(), networkId); - _nicSecondaryIpDao.persist(secondaryIpVO); + NicSecondaryIpVO secondaryIpVO = new NicSecondaryIpVO(nicId, ip4AddrFinal, ip6AddrFinal, vmId, ipOwner.getId(), ipOwner.getDomainId(), networkId); + _nicSecondaryIpDao.persist(secondaryIpVO); return secondaryIpVO.getId(); } }); - return getNicSecondaryIp(id); + return getNicSecondaryIp(id); } else { return null; } diff --git a/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java index 3edc8d217d1..5150ad79e92 100644 --- a/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java +++ b/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java @@ -45,6 +45,7 @@ import com.cloud.network.Network.State; import com.cloud.network.NetworkModel; import com.cloud.network.NetworkProfile; +import com.cloud.network.NetworkService; import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.Networks.Mode; import com.cloud.network.Networks.TrafficType; @@ -65,6 +66,7 @@ import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.ExceptionUtil; +import com.cloud.utils.net.NetUtils; import com.cloud.vm.Nic; import com.cloud.vm.Nic.ReservationStrategy; import com.cloud.vm.NicProfile; @@ -74,6 +76,7 @@ import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.NicSecondaryIpDao; +import com.cloud.vm.dao.NicSecondaryIpVO; public class DirectNetworkGuru extends AdapterBase implements NetworkGuru { @@ -105,6 +108,10 @@ NetworkOfferingServiceMapDao _ntwkOfferingSrvcDao; @Inject PhysicalNetworkDao _physicalNetworkDao; + @Inject + private NetworkService networkService; + @Inject + private NicSecondaryIpDao nicSecondaryIpDao; private static final TrafficType[] TrafficTypes = {TrafficType.Guest}; protected IsolationMethod[] _isolationMethods; @@ -338,9 +345,16 @@ public void doInTransactionWithoutResult(TransactionStatus status) { List<String> nicSecIps = null; nicSecIps = _nicSecondaryIpDao.getSecondaryIpAddressesForNic(nic.getId()); for (String secIp : nicSecIps) { - IPAddressVO pubIp = _ipAddressDao.findByIpAndSourceNetworkId(nic.getNetworkId(), secIp); - _ipAddrMgr.markIpAsUnavailable(pubIp.getId()); - _ipAddressDao.unassignIpAddress(pubIp.getId()); + if (NetUtils.isValidIp4(secIp)) { + IPAddressVO pubIp = _ipAddressDao.findByIpAndSourceNetworkId(nic.getNetworkId(), secIp); + _ipAddrMgr.markIpAsUnavailable(pubIp.getId()); + _ipAddressDao.unassignIpAddress(pubIp.getId()); + } else { + NicSecondaryIpVO nicSecIp = nicSecondaryIpDao.findByIp6AddressAndNetworkId(secIp, nic.getNetworkId()); + if (nicSecIp != null) { + networkService.releaseSecondaryIpFromNic(nicSecIp.getId()); + } + } } } }); diff --git a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java index eda7ca4e76f..2809005a705 100644 --- a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java +++ b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java @@ -19,6 +19,9 @@ import com.cloud.domain.DomainVO; import com.cloud.usage.UsageVO; import com.cloud.user.AccountVO; +import com.cloud.vm.NicSecondaryIp; + +import org.apache.cloudstack.api.response.NicSecondaryIpResponse; import org.apache.cloudstack.api.response.UsageRecordResponse; import org.apache.cloudstack.usage.UsageService; import org.junit.Before; @@ -37,6 +40,7 @@ import java.util.TimeZone; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyLong; import static org.mockito.Mockito.when; @@ -109,4 +113,32 @@ public void testUsageRecordResponse(){ UsageRecordResponse MockResponse = helper.createUsageResponse(usage); assertEquals("DomainName",MockResponse.getDomainName()); } + + @Test + public void setResponseIpAddressTestIpv4() { + NicSecondaryIp result = Mockito.mock(NicSecondaryIp.class); + NicSecondaryIpResponse response = new NicSecondaryIpResponse(); + setResult(result, "ipv4", "ipv6"); + + ApiResponseHelper.setResponseIpAddress(result, response); + + assertTrue(response.getIpAddr().equals("ipv4")); + } + + private void setResult(NicSecondaryIp result, String ipv4, String ipv6) { + when(result.getIp4Address()).thenReturn(ipv4); + when(result.getIp6Address()).thenReturn(ipv6); + } + + @Test + public void setResponseIpAddressTestIpv6() { + NicSecondaryIp result = Mockito.mock(NicSecondaryIp.class); + NicSecondaryIpResponse response = new NicSecondaryIpResponse(); + setResult(result, null, "ipv6"); + + ApiResponseHelper.setResponseIpAddress(result, response); + + assertTrue(response.getIpAddr().equals("ipv6")); + } + } diff --git a/server/src/test/java/com/cloud/network/IpAddressManagerTest.java b/server/src/test/java/com/cloud/network/IpAddressManagerTest.java index 0bf92ee2f69..2bf989c7c1f 100644 --- a/server/src/test/java/com/cloud/network/IpAddressManagerTest.java +++ b/server/src/test/java/com/cloud/network/IpAddressManagerTest.java @@ -17,27 +17,29 @@ package com.cloud.network; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import com.cloud.network.Network.Service; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.rules.StaticNat; import com.cloud.network.rules.StaticNatImpl; import com.cloud.utils.net.Ip; -import static org.mockito.Mockito.when; - -import java.util.Collections; -import java.util.List; - -import static org.mockito.Mockito.anyLong; -import static org.mockito.Mockito.mock; - public class IpAddressManagerTest { @Mock @@ -46,6 +48,9 @@ @InjectMocks IpAddressManagerImpl _ipManager; + @InjectMocks + NetworkModelImpl networkModel = Mockito.spy(new NetworkModelImpl()); + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -68,4 +73,68 @@ public void testGetStaticNatSourceIps() { IPAddressVO returnedVO = ips.get(0); Assert.assertEquals(vo, returnedVO); } -} + + @Test + public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestRequestedIpEqualsIp6Gateway() { + Network network = setTestIsIpEqualsGatewayOrNetworkOfferingsEmpty(0l, "gateway", "ip6Gateway", null, new ArrayList<Service>()); + + boolean result = _ipManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "ip6Gateway"); + + Mockito.verify(networkModel, Mockito.times(0)).listNetworkOfferingServices(Mockito.anyLong()); + Assert.assertTrue(result); + } + + @Test + public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestRequestedIpEqualsGateway() { + Network network = setTestIsIpEqualsGatewayOrNetworkOfferingsEmpty(0l, "gateway", "ip6Gateway", null, new ArrayList<Service>()); + + boolean result = _ipManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "gateway"); + + Mockito.verify(networkModel, Mockito.times(0)).listNetworkOfferingServices(Mockito.anyLong()); + Assert.assertTrue(result); + } + + @Test + public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestExpectFalseServicesNotEmpty() { + List<Service> services = new ArrayList<Service>(); + Service serviceGateway = new Service("Gateway"); + services.add(serviceGateway); + Network network = setTestIsIpEqualsGatewayOrNetworkOfferingsEmpty(0l, "gateway", "ip6Gateway", null, services); + + boolean result = _ipManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "requestedIp"); + + Mockito.verify(networkModel).listNetworkOfferingServices(Mockito.anyLong()); + Assert.assertFalse(result); + } + + @Test + public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestExpectFalseServicesCidrNotNull() { + Network network = setTestIsIpEqualsGatewayOrNetworkOfferingsEmpty(0l, "gateway", "ip6Gateway", "cidr", new ArrayList<Service>()); + + boolean result = _ipManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "requestedIp"); + + Mockito.verify(networkModel).listNetworkOfferingServices(Mockito.anyLong()); + Assert.assertFalse(result); + } + + @Test + public void isIpEqualsGatewayOrNetworkOfferingsEmptyTestNetworkOfferingsEmptyAndCidrNull() { + Network network = setTestIsIpEqualsGatewayOrNetworkOfferingsEmpty(0l, "gateway", "ip6Gateway", null, new ArrayList<Service>()); + + boolean result = _ipManager.isIpEqualsGatewayOrNetworkOfferingsEmpty(network, "requestedIp"); + + Mockito.verify(networkModel).listNetworkOfferingServices(Mockito.anyLong()); + Assert.assertTrue(result); + } + + private Network setTestIsIpEqualsGatewayOrNetworkOfferingsEmpty(long networkOfferingId, String gateway, String ip6Gateway, String cidr, List<Service> services) { + Network network = mock(Network.class); + Mockito.when(network.getNetworkOfferingId()).thenReturn(networkOfferingId); + Mockito.when(network.getGateway()).thenReturn(gateway); + Mockito.when(network.getIp6Gateway()).thenReturn(ip6Gateway); + Mockito.when(network.getCidr()).thenReturn(cidr); + Mockito.doReturn(services).when(networkModel).listNetworkOfferingServices(Mockito.anyLong()); + return network; + } + +} \ No newline at end of file diff --git a/server/src/test/java/com/cloud/network/Ipv6AddressManagerTest.java b/server/src/test/java/com/cloud/network/Ipv6AddressManagerTest.java new file mode 100644 index 00000000000..26ca8f450ab --- /dev/null +++ b/server/src/test/java/com/cloud/network/Ipv6AddressManagerTest.java @@ -0,0 +1,229 @@ +// 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. + +package com.cloud.network; + +import static org.mockito.Mockito.mock; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.IpAddress.State; +import com.cloud.network.Network.IpAddresses; +import com.cloud.network.dao.IPAddressDaoImpl; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.UserIpv6AddressDaoImpl; +import com.cloud.user.Account; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.dao.NicSecondaryIpDaoImpl; +import com.cloud.vm.dao.NicSecondaryIpVO; + +public class Ipv6AddressManagerTest { + + @InjectMocks + Ipv6AddressManagerImpl ip6Manager = Mockito.spy(new Ipv6AddressManagerImpl()); + + @InjectMocks + NicSecondaryIpDaoImpl nicSecondaryIpDao = Mockito.spy(new NicSecondaryIpDaoImpl()); + + @InjectMocks + UserIpv6AddressDaoImpl ipv6Dao = Mockito.spy(new UserIpv6AddressDaoImpl()); + + @InjectMocks + IpAddressManagerImpl ipAddressManager = Mockito.spy(new IpAddressManagerImpl()); + + @InjectMocks + NetworkModelImpl networkModel = Mockito.mock(NetworkModelImpl.class);// = Mockito.spy(new NetworkModelImpl()); + + @InjectMocks + IPAddressDaoImpl ipAddressDao = Mockito.spy(new IPAddressDaoImpl()); + + private Network network = mockNetwork(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void isIp6TakenTestNoNull() { + setIsIp6TakenTest(new UserIpv6AddressVO(), new NicSecondaryIpVO(0l, "ipaddr", 0l, 0l, 0l, 0l)); + boolean result = ip6Manager.isIp6Taken(network, "requestedIpv6"); + assertAndVerifyIsIp6Taken(true, result); + } + + @Test + public void isIp6TakenTestSecIpNull() { + setIsIp6TakenTest(new UserIpv6AddressVO(), null); + boolean result = ip6Manager.isIp6Taken(network, "requestedIpv6"); + assertAndVerifyIsIp6Taken(true, result); + } + + @Test + public void isIp6TakenTestUserIpv6AddressNull() { + setIsIp6TakenTest(null, new NicSecondaryIpVO(0l, "ipaddr", 0l, 0l, 0l, 0l)); + boolean result = ip6Manager.isIp6Taken(network, "requestedIpv6"); + assertAndVerifyIsIp6Taken(true, result); + } + + @Test + public void isIp6TakenTestAllNull() { + setIsIp6TakenTest(null, null); + boolean result = ip6Manager.isIp6Taken(network, "requestedIpv6"); + assertAndVerifyIsIp6Taken(false, result); + } + + private void assertAndVerifyIsIp6Taken(boolean expected, boolean result) { + Assert.assertEquals(expected, result); + Mockito.verify(ipv6Dao).findByNetworkIdAndIp(Mockito.anyLong(), Mockito.anyString()); + Mockito.verify(nicSecondaryIpDao).findByIp6AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong()); + } + + private void setIsIp6TakenTest(UserIpv6AddressVO userIpv6, NicSecondaryIpVO nicSecondaryIp) { + Mockito.doReturn(userIpv6).when(ipv6Dao).findByNetworkIdAndIp(Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(nicSecondaryIp).when(nicSecondaryIpDao).findByIp6AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong()); + } + + private Network mockNetwork() { + Network network = mock(Network.class); + Mockito.when(network.getId()).thenReturn(0l); + Mockito.when(network.getIp6Cidr()).thenReturn("2001:db8::/32"); + return network; + } + + @Test + public void allocatePublicIp6ForGuestNicTestNoException() throws InsufficientAddressCapacityException { + Account owner = Mockito.mock(Account.class); + String requestedIpv6 = setCheckIfCanAllocateIpv6AddresscTest("2001:db8::10", false, false); + + String returnedIp = ip6Manager.allocatePublicIp6ForGuestNic(network, 0l, owner, requestedIpv6); + + Mockito.verify(ip6Manager).checkIfCanAllocateIpv6Address(network, requestedIpv6); + Assert.assertEquals(requestedIpv6, returnedIp); + } + + @Test(expected = InsufficientAddressCapacityException.class) + public void checkIfCanAllocateIpv6AddressTestIp6IsTaken() throws InsufficientAddressCapacityException { + String requestedIpv6 = setCheckIfCanAllocateIpv6AddresscTest("2001:db8::10", true, false); + + ip6Manager.checkIfCanAllocateIpv6Address(network, requestedIpv6); + + verifyCheckIfCanAllocateIpv6AddressTest(network, requestedIpv6, 1, 0); + } + + @Test(expected = InvalidParameterValueException.class) + public void checkIfCanAllocateIpv6AddressTestIpIsIpEqualsGatewayOrNetworkOfferingsEmpty() throws InsufficientAddressCapacityException { + String requestedIpv6 = setCheckIfCanAllocateIpv6AddresscTest("2001:db8::10", false, true); + + ip6Manager.checkIfCanAllocateIpv6Address(network, requestedIpv6); + + verifyCheckIfCanAllocateIpv6AddressTest(network, requestedIpv6, 1, 1); + } + + @Test(expected = InvalidParameterValueException.class) + public void checkIfCanAllocateIpv6AddressTestIpINotInTheNetwork() throws InsufficientAddressCapacityException { + String requestedIpv6 = "2002:db8::10"; + setCheckIfCanAllocateIpv6AddresscTest(requestedIpv6, false, false); + + ip6Manager.checkIfCanAllocateIpv6Address(network, requestedIpv6); + + verifyCheckIfCanAllocateIpv6AddressTest(network, requestedIpv6, 1, 1); + } + + private void verifyCheckIfCanAllocateIpv6AddressTest(Network network, String requestedIpv6, int isIp6TakenTimes, int isIpEqualsGatewayTimes) { + Mockito.verify(ip6Manager, Mockito.times(isIp6TakenTimes)).isIp6Taken(network, requestedIpv6); + Mockito.verify(ipAddressManager, Mockito.times(isIpEqualsGatewayTimes)).isIpEqualsGatewayOrNetworkOfferingsEmpty(network, requestedIpv6); + } + + private String setCheckIfCanAllocateIpv6AddresscTest(String requestedIpv6, boolean isIp6Taken, boolean isIpEqualsGatewayOrNetworkOfferingsEmpty) { + Mockito.doReturn(isIp6Taken).when(ip6Manager).isIp6Taken(Mockito.eq(network), Mockito.anyString()); + Mockito.doReturn(isIpEqualsGatewayOrNetworkOfferingsEmpty).when(ipAddressManager).isIpEqualsGatewayOrNetworkOfferingsEmpty(network, requestedIpv6); + NetUtils.isIp6InNetwork(requestedIpv6, network.getIp6Cidr()); + return requestedIpv6; + } + + @Test + public void acquireGuestIpv6AddressTest() throws InsufficientAddressCapacityException { + setAcquireGuestIpv6AddressTest(true, State.Free); + String requestedIpv6 = setCheckIfCanAllocateIpv6AddresscTest("2001:db8::10", false, false); + + ip6Manager.acquireGuestIpv6Address(network, requestedIpv6); + + verifyAcquireGuestIpv6AddressTest(); + } + + private void verifyAcquireGuestIpv6AddressTest() { + Mockito.verify(networkModel).areThereIPv6AddressAvailableInNetwork(Mockito.anyLong()); + Mockito.verify(networkModel).checkRequestedIpAddresses(Mockito.anyLong(), Mockito.any(IpAddresses.class)); + Mockito.verify(ipAddressDao).findByIpAndSourceNetworkId(Mockito.anyLong(), Mockito.anyString()); + } + + @Test(expected = InsufficientAddressCapacityException.class) + public void acquireGuestIpv6AddressTestUnavailableIp() throws InsufficientAddressCapacityException { + setAcquireGuestIpv6AddressTest(false, State.Free); + String requestedIpv6 = setCheckIfCanAllocateIpv6AddresscTest("2001:db8::10", false, false); + + ip6Manager.acquireGuestIpv6Address(network, requestedIpv6); + + verifyAcquireGuestIpv6AddressTest(); + } + + @Test(expected = InsufficientAddressCapacityException.class) + public void acquireGuestIpv6AddressTestStateAllocating() throws InsufficientAddressCapacityException { + setAcquireGuestIpv6AddressTest(false, State.Allocating); + String requestedIpv6 = setCheckIfCanAllocateIpv6AddresscTest("2001:db8::10", false, false); + + ip6Manager.acquireGuestIpv6Address(network, requestedIpv6); + + verifyAcquireGuestIpv6AddressTest(); + } + + @Test(expected = InsufficientAddressCapacityException.class) + public void acquireGuestIpv6AddressTestStateAllocated() throws InsufficientAddressCapacityException { + setAcquireGuestIpv6AddressTest(false, State.Allocated); + String requestedIpv6 = setCheckIfCanAllocateIpv6AddresscTest("2001:db8::10", false, false); + + ip6Manager.acquireGuestIpv6Address(network, requestedIpv6); + + verifyAcquireGuestIpv6AddressTest(); + } + + @Test(expected = InsufficientAddressCapacityException.class) + public void acquireGuestIpv6AddressTestStateReleasing() throws InsufficientAddressCapacityException { + setAcquireGuestIpv6AddressTest(false, State.Releasing); + String requestedIpv6 = setCheckIfCanAllocateIpv6AddresscTest("2001:db8::10", false, false); + + ip6Manager.acquireGuestIpv6Address(network, requestedIpv6); + + verifyAcquireGuestIpv6AddressTest(); + } + + private void setAcquireGuestIpv6AddressTest(boolean isIPAvailable, State state) { + mockNetwork(); + IPAddressVO ipVo = Mockito.mock(IPAddressVO.class); + Mockito.doReturn(isIPAvailable).when(networkModel).areThereIPv6AddressAvailableInNetwork(Mockito.anyLong()); + Mockito.doReturn(ipVo).when(ipAddressDao).findByIpAndSourceNetworkId(Mockito.anyLong(), Mockito.anyString()); + Mockito.when(ipVo.getState()).thenReturn(state); + } + +} diff --git a/server/src/test/java/com/cloud/network/MockNetworkModelImpl.java b/server/src/test/java/com/cloud/network/MockNetworkModelImpl.java index d14255432e7..a35cec59b1b 100644 --- a/server/src/test/java/com/cloud/network/MockNetworkModelImpl.java +++ b/server/src/test/java/com/cloud/network/MockNetworkModelImpl.java @@ -813,7 +813,7 @@ public boolean isNetworkInlineMode(Network network) { } @Override - public boolean isIP6AddressAvailableInNetwork(long networkId) { + public boolean areThereIPv6AddressAvailableInNetwork(long networkId) { // TODO Auto-generated method stub return false; } diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index 8cbf30cdc88..57ecbd1fdc6 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -51,6 +51,7 @@ import com.cloud.network.GuestVlan; import com.cloud.network.IpAddress; import com.cloud.network.Network; +import com.cloud.network.Network.IpAddresses; import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; import com.cloud.network.NetworkProfile; @@ -827,7 +828,7 @@ public boolean isSecondaryIpSetForNic(long nicId) { } @Override - public NicSecondaryIp allocateSecondaryGuestIP(long nicId, String ipaddress) { + public NicSecondaryIp allocateSecondaryGuestIP(long nicId, IpAddresses requestedIpPair) { // TODO Auto-generated method stub return null; } diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkModelImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkModelImpl.java index 33192a75c59..56670d261cf 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkModelImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkModelImpl.java @@ -829,7 +829,7 @@ public boolean isNetworkInlineMode(Network network) { } @Override - public boolean isIP6AddressAvailableInNetwork(long networkId) { + public boolean areThereIPv6AddressAvailableInNetwork(long networkId) { // TODO Auto-generated method stub return false; } diff --git a/ui/lib/jquery.validate.additional-methods.js b/ui/lib/jquery.validate.additional-methods.js index 811b4f76cd3..2de65d36401 100644 --- a/ui/lib/jquery.validate.additional-methods.js +++ b/ui/lib/jquery.validate.additional-methods.js @@ -503,6 +503,16 @@ $.validator.addMethod("ipv6", function(value, element) { return this.optional(element) || /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test(value); }, "Please enter a valid IP v6 address."); +$.validator.addMethod("ipv46address", function(value, element) { + if (this.optional(element) && value.length == 0) + return true; + + if ($.validator.methods.ipv4.call(this, value, element) || $.validator.methods.ipv6.call(this, value, element)) + return true; + + return false; +}, "Please enter a valid IPv4/IPv6 address."); + $.validator.addMethod("lettersonly", function(value, element) { return this.optional(element) || /^[a-z]+$/i.test(value); }, "Letters only please"); diff --git a/ui/scripts/network.js b/ui/scripts/network.js index 22ddb10d8cf..1cbacb6aabb 100644 --- a/ui/scripts/network.js +++ b/ui/scripts/network.js @@ -1782,7 +1782,7 @@ label: 'label.ip.address', validation: { required: false, - ipv4: true + ipv46address: true } } } diff --git a/utils/src/main/java/com/cloud/utils/net/NetUtils.java b/utils/src/main/java/com/cloud/utils/net/NetUtils.java index 1bd08a32b25..9a88249c69c 100644 --- a/utils/src/main/java/com/cloud/utils/net/NetUtils.java +++ b/utils/src/main/java/com/cloud/utils/net/NetUtils.java @@ -23,9 +23,9 @@ import java.io.IOException; import java.io.InputStreamReader; import java.math.BigInteger; -import java.net.InetAddress; import java.net.Inet4Address; import java.net.Inet6Address; +import java.net.InetAddress; import java.net.InterfaceAddress; import java.net.NetworkInterface; import java.net.SocketException; @@ -1547,4 +1547,20 @@ public static IPv6Address ipv6LinkLocal(final String macAddress) { return EUI64Address(IPv6Network.LINK_LOCAL_NETWORK, macAddress); } + /** + * Returns true if the given IP address is IPv4 or false if it is an IPv6. If it is an invalid IP address it throws an exception. + */ + public static boolean isIpv4(String ipAddr) { + boolean isIpv4 = true; + if (ipAddr != null) { + if (!NetUtils.isValidIp4(ipAddr)) { + isIpv4 = false; + } + if (!NetUtils.isValidIp6(ipAddr) && !isIpv4) { + throw new IllegalArgumentException("Invalid ip address " + ipAddr); + } + } + return isIpv4; + } + } diff --git a/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java b/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java index bec22098b49..4abb87af4f5 100644 --- a/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java +++ b/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java @@ -678,4 +678,20 @@ public void testIsValidPort() { assertFalse(NetUtils.isValidPort(-1)); assertFalse(NetUtils.isValidPort(65536)); } + + @Test + public void testIsIpv4() { + assertTrue(NetUtils.isIpv4("192.168.1.1")); + assertFalse(NetUtils.isIpv4("2a01:4f8:130:2192::2")); + } + + @Test(expected = IllegalArgumentException.class) + public void testIsIpv4ExpectException() { + NetUtils.isIpv4("test"); + } + + @Test(expected = IllegalArgumentException.class) + public void testIsIpv4ExpectException2() { + NetUtils.isIpv4("2001:db8:300::/64"); + } } ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org > Add Secondary IPv6 address through API > -------------------------------------- > > Key: CLOUDSTACK-10328 > URL: https://issues.apache.org/jira/browse/CLOUDSTACK-10328 > Project: CloudStack > Issue Type: New Feature > Security Level: Public(Anyone can view this level - this is the > default.) > Reporter: Gabriel Beims Bräscher > Assignee: Gabriel Beims Bräscher > Priority: Critical > > Currently, we have IPv6 support in Basic Networking. However, CloudStack does > not support Multiple IPv6 addresses. This was partially implemented with this > Pull Request: https://github.com/apache/cloudstack/pull/2028 > The addIpToNic command only supports IPv4: > https://cloudstack.apache.org/api/apidocs-4.11/apis/addIpToNic.html > Features: > - Add IPv6 address alias > - Check if this IPv6 address is in the configured subnet for that POD/VLAN > - Update the security group > The admin will manually add the address to the Instance but the Security > Grouping should allow it. -- This message was sent by Atlassian JIRA (v7.6.3#76005)