From 4a914aa88dda74107af3b7f0c093501726d0ee45 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 26 Apr 2022 07:21:32 +0530 Subject: [PATCH] network: ipv6 static routes (#5786) * wip Signed-off-by: Abhishek Kumar * IPv6: configure VR of isolated networks * IPv6: add default IPv6 route in VR of isolated networks * Reformat server/src/main/java/com/cloud/network/NetworkServiceImpl.java * IPv6: update network to offering which support IPv6 * IPv6: update vm nic ipv6 address when update network to new offering * IPv6: configure VPC VR to support multiple tiers with IPv6 * IPv6: add RDNSS in radvd.conf * IPv6/UI: support ipv6 protocols in Network ACL * wip Signed-off-by: Abhishek Kumar * changes for diagnostics Signed-off-by: Abhishek Kumar * more import fromo #5594 Signed-off-by: Abhishek Kumar * IPv6: fix wrong public ipv6 in VPC VR * changes Signed-off-by: Abhishek Kumar * fix Signed-off-by: Abhishek Kumar * Update server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java Co-authored-by: dahn * ui: fix add ipv6 prefix labels, message Signed-off-by: Abhishek Kumar * ui: label fix Signed-off-by: Abhishek Kumar * logging fix Signed-off-by: Abhishek Kumar * test fix Signed-off-by: Abhishek Kumar * changes Signed-off-by: Abhishek Kumar * minor ui refactor Signed-off-by: Abhishek Kumar * ip6 events Signed-off-by: Abhishek Kumar * ip6 usage Signed-off-by: Abhishek Kumar * unused Signed-off-by: Abhishek Kumar * slaac based public ip Signed-off-by: Abhishek Kumar * fix Signed-off-by: Abhishek Kumar * remove unused Signed-off-by: Abhishek Kumar * diagnostics fix for vr Signed-off-by: Abhishek Kumar * firewall changes Signed-off-by: Abhishek Kumar * alert and show ipv6 usage Signed-off-by: Abhishek Kumar * fix Signed-off-by: Abhishek Kumar * change for network response Signed-off-by: Abhishek Kumar * ipv6 network test Signed-off-by: Abhishek Kumar * changes Signed-off-by: Abhishek Kumar * ui: fix ipaddress listing Signed-off-by: Abhishek Kumar * wip Signed-off-by: Abhishek Kumar * fix simulator Signed-off-by: Abhishek Kumar * changes Signed-off-by: Abhishek Kumar * changes Signed-off-by: Abhishek Kumar * test fix Signed-off-by: Abhishek Kumar * test and fixes Signed-off-by: Abhishek Kumar * test temp change revert Signed-off-by: Abhishek Kumar * fixes Signed-off-by: Abhishek Kumar * use uuid Signed-off-by: Abhishek Kumar * event syntax fix Signed-off-by: Abhishek Kumar * wip Signed-off-by: Abhishek Kumar * review comments Signed-off-by: Abhishek Kumar * assign vlan public IP for dualstack only if both protocols present on same vlan Signed-off-by: Abhishek Kumar * internetprotocol in networkofferingresponse Signed-off-by: Abhishek Kumar * add tcp, udp Signed-off-by: Abhishek Kumar * support vpc with ipv6 only on same vlan - adds new internet protocol param to createVpcOffering API - When DualStack internet protocol is selected for the VPC offering, tiers with network with or without IPv6 support can be deployed. - When IPv4 internet protocol is used for the VPC offering, tiers with network with only IPv4 support can be deployed Signed-off-by: Abhishek Kumar * change and fix allow VPC with IPv4 protocol to deploy tiers with IPv6 Signed-off-by: Abhishek Kumar * fix Signed-off-by: Abhishek Kumar * test fix Signed-off-by: Abhishek Kumar * ui fixes Signed-off-by: Abhishek Kumar * fix multiple routes, network guest ipv6 gateway Signed-off-by: Abhishek Kumar * address review comments Signed-off-by: Abhishek Kumar * stop radvd on backup VR Signed-off-by: Abhishek Kumar * fix router redundant status with ipv6 Signed-off-by: Abhishek Kumar * disable radvd for backup vr Signed-off-by: Abhishek Kumar * correctly set ipv6 in redundant router case Signed-off-by: Abhishek Kumar * remove unused code Signed-off-by: Abhishek Kumar * fix connection Signed-off-by: Abhishek Kumar * ui: don't show all protocol for egress Signed-off-by: Abhishek Kumar * fix guest ipv6 for redundant VRs Redundant VRs will not be assigned an IPv6 by ACS and guest netwrok gateway will be added as IPv6 for guest interface by systemvm scripts during setting redundant state of the VR. Signed-off-by: Abhishek Kumar * fix missing ipv6 on redundant vr Signed-off-by: Abhishek Kumar * fix syntax Signed-off-by: Abhishek Kumar * ui: fix vpc tier redirect to show details When redirecting to VPC tier, details tab should be active by default Signed-off-by: Abhishek Kumar * restart radvd on primary redundant vr Signed-off-by: Abhishek Kumar * check for ipv6 values Signed-off-by: Abhishek Kumar * remove old ui change Signed-off-by: Abhishek Kumar * fix condition Signed-off-by: Abhishek Kumar * remove gateway from backup vr Signed-off-by: Abhishek Kumar * network upgrade fail early when IPv6 network cannot be allocated fail before shutting down the network Signed-off-by: Abhishek Kumar * fix radvd not running on RVR Signed-off-by: Abhishek Kumar * prepare radvd.conf once Signed-off-by: Abhishek Kumar * fix job polling Signed-off-by: Abhishek Kumar * fix RVR for vpc with ipv6 Signed-off-by: Abhishek Kumar * fix ipv6 network acls Signed-off-by: Abhishek Kumar * Update CsConfig.py * add check Signed-off-by: Abhishek Kumar * test: vpc offering test Signed-off-by: Abhishek Kumar * test: add negative tests for guest prefix, public range Signed-off-by: Abhishek Kumar * add default ipv6 route for primary Signed-off-by: Abhishek Kumar * fix dadfailed on vpc rvr Signed-off-by: Abhishek Kumar * ui: fix add iprange form, dedicate action visibility Signed-off-by: Abhishek Kumar * fix adding, deleting ipv6 range Signed-off-by: Abhishek Kumar * fix failing test Signed-off-by: Abhishek Kumar * fix missing destination cidr in ipv6 firewall Signed-off-by: Abhishek Kumar * fix ipv6 nftables rules Allow storing linger IPv6 CIDRs in DB Specify all port range for TC{, UDP protocol rules withot ports Fix adding nft rules by creating chains first Signed-off-by: Abhishek Kumar * fix icmpv6 type, code Signed-off-by: Abhishek Kumar * fix icmp type, code Signed-off-by: Abhishek Kumar * test: add more for ipv6 network Signed-off-by: Abhishek Kumar * add warning message for egress policy in ipv6 fw rule Signed-off-by: Abhishek Kumar * ui,server: update ipv6 vlan range Signed-off-by: Abhishek Kumar * subnet operations inside transaction Signed-off-by: Abhishek Kumar * server: persistent public IPv6 for network Signed-off-by: Abhishek Kumar * ui: fix action alignment Signed-off-by: Abhishek Kumar * fix vpc acl for tiers Signed-off-by: Abhishek Kumar * fix removing network placeholder nic Signed-off-by: Abhishek Kumar * fix acl rules for ip version Signed-off-by: Abhishek Kumar * fix placeholder nic and nd-neighbor block issue Signed-off-by: Abhishek Kumar * test for redundant nw Signed-off-by: Abhishek Kumar * fix ping Signed-off-by: Abhishek Kumar * systemvm: uprgade to debian 11.3.0 * ipv6: enable ipv6 in sysctl config in bootstrap.sh * VR: fix KeyError: 'nic_ip6_cidr' * build fix for latest event changes Signed-off-by: Abhishek Kumar Co-authored-by: Wei Zhou Co-authored-by: dahn --- .../cloud/agent/api/to/FirewallRuleTO.java | 3 + .../com/cloud/agent/api/to/NetworkTO.java | 9 + .../java/com/cloud/capacity/Capacity.java | 1 + .../configuration/ConfigurationService.java | 19 + .../cloud/dc/DataCenterGuestIpv6Prefix.java | 32 + .../main/java/com/cloud/event/EventTypes.java | 19 + .../Ipv6GuestPrefixSubnetNetworkMap.java | 37 + .../java/com/cloud/network/Ipv6Service.java | 94 ++ .../main/java/com/cloud/network/Network.java | 25 +- .../com/cloud/network/rules/FirewallRule.java | 2 +- .../network/vpc/VpcProvisioningService.java | 3 +- .../com/cloud/offering/NetworkOffering.java | 6 +- .../apache/cloudstack/alert/AlertService.java | 1 + .../apache/cloudstack/api/ApiConstants.java | 12 +- .../org/apache/cloudstack/api/BaseCmd.java | 3 + .../cloudstack/api/ResponseGenerator.java | 38 +- .../CreateGuestNetworkIpv6PrefixCmd.java | 115 ++ .../CreateManagementNetworkIpRangeCmd.java | 2 +- .../network/CreateNetworkOfferingCmd.java | 10 + .../DeleteGuestNetworkIpv6PrefixCmd.java | 101 ++ .../ListGuestNetworkIpv6PrefixesCmd.java | 88 ++ .../admin/vpc/CreateVPCOfferingCmd.java | 16 +- .../user/ipv6/CreateIpv6FirewallRuleCmd.java | 255 ++++ .../user/ipv6/DeleteIpv6FirewallRuleCmd.java | 97 ++ .../user/ipv6/ListIpv6FirewallRulesCmd.java | 118 ++ .../user/ipv6/UpdateIpv6FirewallRuleCmd.java | 175 +++ .../user/network/CreateNetworkACLCmd.java | 1 + .../DataCenterGuestIpv6PrefixResponse.java | 87 ++ .../api/response/FirewallResponse.java | 8 + .../api/response/IpRangeResponse.java | 27 +- .../api/response/Ipv6RouteResponse.java | 56 + .../api/response/NetworkOfferingResponse.java | 12 + .../api/response/NetworkResponse.java | 30 + .../api/response/VlanIpRangeResponse.java | 21 +- .../api/response/VpcOfferingResponse.java | 12 + .../cloudstack/api/response/VpcResponse.java | 9 + .../diagnostics/DiagnosticsType.java | 2 +- .../agent/api/SetupGuestNetworkCommand.java | 45 + .../routing/SetIpv6FirewallRulesAnswer.java | 39 + .../routing/SetIpv6FirewallRulesCommand.java | 49 + .../api/routing/SetNetworkACLCommand.java | 16 +- .../resource/virtualnetwork/VRScripts.java | 1 + .../facade/AbstractConfigItemFacade.java | 3 + .../facade/SetGuestNetworkConfigItem.java | 24 +- .../SetIpv6FirewallRulesConfigItem.java | 59 + .../facade/SetNetworkAclConfigItem.java | 4 +- .../virtualnetwork/model/ConfigBase.java | 1 + .../virtualnetwork/model/GuestNetwork.java | 75 +- .../virtualnetwork/model/NetworkACL.java | 9 + .../service/NetworkOrchestrationService.java | 2 + .../java/com/cloud/alert/AlertManager.java | 6 + .../configuration/ConfigurationManager.java | 6 +- .../com/cloud/network/vpc/VpcManager.java | 2 +- .../orchestration/NetworkOrchestrator.java | 63 +- .../java/com/cloud/capacity/CapacityVO.java | 1 + .../cloud/dc/DataCenterGuestIpv6PrefixVO.java | 99 ++ .../dc/dao/DataCenterGuestIpv6PrefixDao.java | 28 + .../dao/DataCenterGuestIpv6PrefixDaoImpl.java | 43 + .../main/java/com/cloud/dc/dao/VlanDao.java | 4 + .../java/com/cloud/dc/dao/VlanDaoImpl.java | 47 + .../Ipv6GuestPrefixSubnetNetworkMapVO.java | 129 ++ .../Ipv6GuestPrefixSubnetNetworkMapDao.java | 33 + ...pv6GuestPrefixSubnetNetworkMapDaoImpl.java | 111 ++ .../cloud/network/dao/NetworkDetailsDao.java | 2 +- .../network/dao/NetworkDetailsDaoImpl.java | 7 + .../java/com/cloud/network/dao/NetworkVO.java | 4 +- .../cloud/network/vpc/dao/VpcOfferingDao.java | 5 + .../network/vpc/dao/VpcOfferingDaoImpl.java | 19 + .../vpc/dao/VpcOfferingDetailsDao.java | 1 + .../vpc/dao/VpcOfferingDetailsDaoImpl.java | 26 + .../offerings/dao/NetworkOfferingDao.java | 5 + .../offerings/dao/NetworkOfferingDaoImpl.java | 14 + .../main/java/com/cloud/vm/dao/NicDao.java | 2 + .../java/com/cloud/vm/dao/NicDaoImpl.java | 11 + ...spring-engine-schema-core-daos-context.xml | 2 + .../META-INF/db/schema-41610to41700.sql | 139 ++ .../agent/manager/MockNetworkManager.java | 3 + .../agent/manager/MockNetworkManagerImpl.java | 12 + .../agent/manager/SimulatorManagerImpl.java | 15 +- .../management/ContrailManagerImpl.java | 24 +- scripts/vm/network/vnet/modifyvlan.sh | 6 + .../com/cloud/alert/AlertManagerImpl.java | 55 +- .../java/com/cloud/api/ApiResponseHelper.java | 104 +- .../query/dao/NetworkOfferingJoinDaoImpl.java | 10 +- .../api/query/dao/VpcOfferingJoinDaoImpl.java | 1 + .../api/query/vo/NetworkOfferingJoinVO.java | 6 + .../cloud/api/query/vo/VpcOfferingJoinVO.java | 7 + .../ConfigurationManagerImpl.java | 221 ++- .../cloud/hypervisor/HypervisorGuruBase.java | 1 + .../ExternalFirewallDeviceManagerImpl.java | 2 +- ...ExternalLoadBalancerDeviceManagerImpl.java | 2 +- .../cloud/network/IpAddressManagerImpl.java | 25 +- .../cloud/network/Ipv6AddressManagerImpl.java | 16 +- .../com/cloud/network/Ipv6ServiceImpl.java | 708 ++++++++++ .../network/NetworkMigrationManagerImpl.java | 3 +- .../com/cloud/network/NetworkServiceImpl.java | 625 ++++---- .../network/firewall/FirewallManagerImpl.java | 50 +- .../guru/ExternalGuestNetworkGuru.java | 15 + .../cloud/network/guru/GuestNetworkGuru.java | 69 +- .../cloud/network/guru/PublicNetworkGuru.java | 5 + .../network/router/CommandSetupHelper.java | 125 +- .../network/router/NetworkHelperImpl.java | 11 +- .../VirtualNetworkApplianceManagerImpl.java | 41 +- .../network/vpc/NetworkACLManagerImpl.java | 13 +- .../network/vpc/NetworkACLServiceImpl.java | 2 +- .../com/cloud/network/vpc/VpcManagerImpl.java | 29 +- .../java/com/cloud/vm/UserVmManagerImpl.java | 1 - .../network/topology/BasicNetworkVisitor.java | 6 + .../spring-server-core-managers-context.xml | 2 + .../vpc/MockConfigurationManagerImpl.java | 35 +- .../com/cloud/vpc/MockNetworkManagerImpl.java | 6 + .../com/cloud/vpc/NetworkACLManagerTest.java | 10 + .../cloud/vpc/dao/MockVpcOfferingDaoImpl.java | 10 + .../CreateNetworkOfferingTest.java | 20 +- .../test/resources/createNetworkOffering.xml | 7 + systemvm/debian/etc/radvd.conf.tmpl | 12 + systemvm/debian/opt/cloud/bin/checkrouter.sh | 4 +- systemvm/debian/opt/cloud/bin/configure.py | 290 +++- systemvm/debian/opt/cloud/bin/cs/CsAddress.py | 39 + systemvm/debian/opt/cloud/bin/cs/CsConfig.py | 20 +- systemvm/debian/opt/cloud/bin/cs/CsDatabag.py | 81 ++ .../debian/opt/cloud/bin/cs/CsNetfilter.py | 43 + .../debian/opt/cloud/bin/cs/CsRedundant.py | 105 ++ systemvm/debian/opt/cloud/bin/cs/CsRoute.py | 50 + .../opt/cloud/bin/cs/CsVpcGuestNetwork.py | 116 ++ systemvm/debian/opt/cloud/bin/diagnostics.py | 12 + systemvm/debian/opt/cloud/bin/merge.py | 5 + .../debian/opt/cloud/bin/setup/bootstrap.sh | 9 + systemvm/debian/opt/cloud/bin/setup/common.sh | 57 + test/integration/smoke/test_network_ipv6.py | 1255 +++++++++++++++++ .../appliance/systemvmtemplate/template.json | 4 +- tools/marvin/marvin/config/test_data.py | 9 + tools/marvin/marvin/lib/base.py | 21 +- .../demo/simulator/testcase/libs/base.py | 19 +- ui/public/locales/en.json | 34 +- ui/src/components/view/DetailsTab.vue | 20 +- ui/src/components/view/InfoCard.vue | 21 +- ui/src/config/section/infra/routers.js | 2 +- ui/src/config/section/network.js | 8 +- ui/src/config/section/offering.js | 4 +- ui/src/views/AutogenView.vue | 2 +- ui/src/views/dashboard/CapacityDashboard.vue | 3 +- .../views/infra/network/IpRangesTabGuest.vue | 198 ++- .../views/infra/network/IpRangesTabPublic.vue | 155 +- ui/src/views/network/Ipv6FirewallRulesTab.vue | 535 +++++++ ui/src/views/offering/AddNetworkOffering.vue | 37 + ui/src/views/offering/AddVpcOffering.vue | 44 +- .../java/com/cloud/utils/net/NetUtils.java | 53 +- 148 files changed, 7632 insertions(+), 620 deletions(-) create mode 100644 api/src/main/java/com/cloud/dc/DataCenterGuestIpv6Prefix.java create mode 100644 api/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMap.java create mode 100644 api/src/main/java/com/cloud/network/Ipv6Service.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateGuestNetworkIpv6PrefixCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteGuestNetworkIpv6PrefixCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListGuestNetworkIpv6PrefixesCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/DeleteIpv6FirewallRuleCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/ListIpv6FirewallRulesCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/UpdateIpv6FirewallRuleCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/DataCenterGuestIpv6PrefixResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/Ipv6RouteResponse.java create mode 100644 core/src/main/java/com/cloud/agent/api/routing/SetIpv6FirewallRulesAnswer.java create mode 100644 core/src/main/java/com/cloud/agent/api/routing/SetIpv6FirewallRulesCommand.java create mode 100644 core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetIpv6FirewallRulesConfigItem.java create mode 100644 engine/schema/src/main/java/com/cloud/dc/DataCenterGuestIpv6PrefixVO.java create mode 100644 engine/schema/src/main/java/com/cloud/dc/dao/DataCenterGuestIpv6PrefixDao.java create mode 100644 engine/schema/src/main/java/com/cloud/dc/dao/DataCenterGuestIpv6PrefixDaoImpl.java create mode 100644 engine/schema/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMapVO.java create mode 100644 engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDao.java create mode 100644 engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDaoImpl.java create mode 100644 server/src/main/java/com/cloud/network/Ipv6ServiceImpl.java create mode 100644 systemvm/debian/etc/radvd.conf.tmpl create mode 100755 systemvm/debian/opt/cloud/bin/cs/CsVpcGuestNetwork.py create mode 100644 test/integration/smoke/test_network_ipv6.py create mode 100644 ui/src/views/network/Ipv6FirewallRulesTab.vue diff --git a/api/src/main/java/com/cloud/agent/api/to/FirewallRuleTO.java b/api/src/main/java/com/cloud/agent/api/to/FirewallRuleTO.java index 25e7b5f60b3..d08884d1cbe 100644 --- a/api/src/main/java/com/cloud/agent/api/to/FirewallRuleTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/FirewallRuleTO.java @@ -155,6 +155,9 @@ public class FirewallRuleTO implements InternalIdentity { rule.getIcmpType(), rule.getIcmpCode()); this.trafficType = trafficType; + if (FirewallRule.Purpose.Ipv6Firewall.equals(purpose)) { + this.destCidrList = rule.getDestinationCidrList(); + } } public FirewallRuleTO(FirewallRule rule, String srcVlanTag, String srcIp, FirewallRule.Purpose purpose, FirewallRule.TrafficType trafficType, diff --git a/api/src/main/java/com/cloud/agent/api/to/NetworkTO.java b/api/src/main/java/com/cloud/agent/api/to/NetworkTO.java index 9c888059d93..b106418f068 100644 --- a/api/src/main/java/com/cloud/agent/api/to/NetworkTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/NetworkTO.java @@ -39,6 +39,7 @@ public class NetworkTO { protected boolean isSecurityGroupEnabled; protected String name; protected String ip6address; + protected String ip6gateway; protected String ip6cidr; public NetworkTO() { @@ -68,6 +69,10 @@ public class NetworkTO { this.ip6address = addr; } + public void setIp6Gateway(String gateway) { + this.ip6gateway = gateway; + } + public void setIp6Cidr(String cidr) { this.ip6cidr = cidr; } @@ -161,6 +166,10 @@ public class NetworkTO { return ip6address; } + public String getIp6Gateway() { + return ip6gateway; + } + public String getIp6Cidr() { return ip6cidr; } diff --git a/api/src/main/java/com/cloud/capacity/Capacity.java b/api/src/main/java/com/cloud/capacity/Capacity.java index ba04a82a5de..684490a605c 100644 --- a/api/src/main/java/com/cloud/capacity/Capacity.java +++ b/api/src/main/java/com/cloud/capacity/Capacity.java @@ -30,6 +30,7 @@ public interface Capacity extends InternalIdentity, Identity { public static final short CAPACITY_TYPE_VLAN = 7; public static final short CAPACITY_TYPE_DIRECT_ATTACHED_PUBLIC_IP = 8; public static final short CAPACITY_TYPE_LOCAL_STORAGE = 9; + public static final short CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET = 10; public static final short CAPACITY_TYPE_GPU = 19; public static final short CAPACITY_TYPE_CPU_CORE = 90; diff --git a/api/src/main/java/com/cloud/configuration/ConfigurationService.java b/api/src/main/java/com/cloud/configuration/ConfigurationService.java index 87d2d2feaf1..d5a50684c20 100644 --- a/api/src/main/java/com/cloud/configuration/ConfigurationService.java +++ b/api/src/main/java/com/cloud/configuration/ConfigurationService.java @@ -20,10 +20,13 @@ import java.util.List; import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; +import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd; import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; +import org.apache.cloudstack.api.command.admin.network.DeleteGuestNetworkIpv6PrefixCmd; import org.apache.cloudstack.api.command.admin.network.DeleteManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd; +import org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6PrefixesCmd; import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd; @@ -51,6 +54,7 @@ import org.apache.cloudstack.region.PortableIp; import org.apache.cloudstack.region.PortableIpRange; import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterGuestIpv6Prefix; import com.cloud.dc.Pod; import com.cloud.dc.Vlan; import com.cloud.domain.Domain; @@ -223,6 +227,21 @@ public interface ConfigurationService { */ void updatePodIpRange(UpdatePodManagementNetworkIpRangeCmd cmd) throws ConcurrentOperationException; + /** + * Creates a new IPv6 prefix for a zone. Needs to be >= /64. + */ + DataCenterGuestIpv6Prefix createDataCenterGuestIpv6Prefix(CreateGuestNetworkIpv6PrefixCmd cmd); + + /** + * Lists IPv6 prefixes for a zone. + */ + List listDataCenterGuestIpv6Prefixes(ListGuestNetworkIpv6PrefixesCmd cmd); + + /** + * Deletes an existing IPv6 prefix. + */ + boolean deleteDataCenterGuestIpv6Prefix(DeleteGuestNetworkIpv6PrefixCmd cmd); + /** * Edits a pod in the database. Will not allow you to edit pods that are being used anywhere in the system. * diff --git a/api/src/main/java/com/cloud/dc/DataCenterGuestIpv6Prefix.java b/api/src/main/java/com/cloud/dc/DataCenterGuestIpv6Prefix.java new file mode 100644 index 00000000000..e3b38546485 --- /dev/null +++ b/api/src/main/java/com/cloud/dc/DataCenterGuestIpv6Prefix.java @@ -0,0 +1,32 @@ +// 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.dc; + +import java.util.Date; + +import org.apache.cloudstack.acl.InfrastructureEntity; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface DataCenterGuestIpv6Prefix extends InfrastructureEntity, InternalIdentity, Identity { + Long getDataCenterId(); + + String getPrefix(); + + Date getCreated(); +} diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 842697f3f0b..595499aa1f7 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -32,6 +32,7 @@ import org.apache.cloudstack.ha.HAConfig; import org.apache.cloudstack.usage.Usage; import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterGuestIpv6Prefix; import com.cloud.dc.Pod; import com.cloud.dc.StorageNetworkIpRange; import com.cloud.dc.Vlan; @@ -150,6 +151,10 @@ public class EventTypes { public static final String EVENT_FIREWALL_CLOSE = "FIREWALL.CLOSE"; public static final String EVENT_FIREWALL_UPDATE = "FIREWALL.UPDATE"; + public static final String EVENT_NET_IP6_ASSIGN = "NET.IP6ASSIGN"; + public static final String EVENT_NET_IP6_RELEASE = "NET.IP6RELEASE"; + public static final String EVENT_NET_IP6_UPDATE = "NET.IP6UPDATE"; + public static final String EVENT_FIREWALL_EGRESS_OPEN = "FIREWALL.EGRESS.OPEN"; public static final String EVENT_FIREWALL_EGRESS_CLOSE = "FIREWALL.EGRESS.CLOSE"; public static final String EVENT_FIREWALL_EGRESS_UPDATE = "FIREWALL.EGRESS.UPDATE"; @@ -334,6 +339,9 @@ public class EventTypes { public static final String EVENT_MANAGEMENT_IP_RANGE_DELETE = "MANAGEMENT.IP.RANGE.DELETE"; public static final String EVENT_MANAGEMENT_IP_RANGE_UPDATE = "MANAGEMENT.IP.RANGE.UPDATE"; + public static final String EVENT_GUEST_IP6_PREFIX_CREATE = "GUEST.IP6.PREFIX.CREATE"; + public static final String EVENT_GUEST_IP6_PREFIX_DELETE = "GUEST.IP6.PREFIX.DELETE"; + public static final String EVENT_STORAGE_IP_RANGE_CREATE = "STORAGE.IP.RANGE.CREATE"; public static final String EVENT_STORAGE_IP_RANGE_DELETE = "STORAGE.IP.RANGE.DELETE"; public static final String EVENT_STORAGE_IP_RANGE_UPDATE = "STORAGE.IP.RANGE.UPDATE"; @@ -482,6 +490,11 @@ public class EventTypes { public static final String EVENT_NETWORK_ACL_ITEM_UPDATE = "NETWORK.ACL.ITEM.UPDATE"; public static final String EVENT_NETWORK_ACL_ITEM_DELETE = "NETWORK.ACL.ITEM.DELETE"; + // IPv6 firewall rule + public static final String EVENT_IPV6_FIREWALL_RULE_CREATE = "IPV6.FIREWALL.RULE.CREATE"; + public static final String EVENT_IPV6_FIREWALL_RULE_UPDATE = "IPV6.FIREWALL.RULE.UPDATE"; + public static final String EVENT_IPV6_FIREWALL_RULE_DELETE = "IPV6.FIREWALL.RULE.DELETE"; + // VPC offerings public static final String EVENT_VPC_OFFERING_CREATE = "VPC.OFFERING.CREATE"; public static final String EVENT_VPC_OFFERING_UPDATE = "VPC.OFFERING.UPDATE"; @@ -704,6 +717,9 @@ public class EventTypes { entityEventDetails.put(EVENT_FIREWALL_EGRESS_OPEN, FirewallRule.class); entityEventDetails.put(EVENT_FIREWALL_EGRESS_CLOSE, FirewallRule.class); entityEventDetails.put(EVENT_FIREWALL_EGRESS_UPDATE, FirewallRule.class); + entityEventDetails.put(EVENT_NET_IP6_ASSIGN, Network.class); + entityEventDetails.put(EVENT_NET_IP6_RELEASE, Network.class); + entityEventDetails.put(EVENT_NET_IP6_UPDATE, Network.class); // Nic Events entityEventDetails.put(EVENT_NIC_CREATE, Nic.class); @@ -845,6 +861,9 @@ public class EventTypes { entityEventDetails.put(EVENT_MANAGEMENT_IP_RANGE_CREATE, Pod.class); entityEventDetails.put(EVENT_MANAGEMENT_IP_RANGE_DELETE, Pod.class); + entityEventDetails.put(EVENT_GUEST_IP6_PREFIX_CREATE, DataCenterGuestIpv6Prefix.class); + entityEventDetails.put(EVENT_GUEST_IP6_PREFIX_DELETE, DataCenterGuestIpv6Prefix.class); + entityEventDetails.put(EVENT_STORAGE_IP_RANGE_CREATE, StorageNetworkIpRange.class); entityEventDetails.put(EVENT_STORAGE_IP_RANGE_DELETE, StorageNetworkIpRange.class); entityEventDetails.put(EVENT_STORAGE_IP_RANGE_UPDATE, StorageNetworkIpRange.class); diff --git a/api/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMap.java b/api/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMap.java new file mode 100644 index 00000000000..48f42a0ec92 --- /dev/null +++ b/api/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMap.java @@ -0,0 +1,37 @@ +// 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 org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface Ipv6GuestPrefixSubnetNetworkMap extends Identity, InternalIdentity { + enum State { + Allocating, // The subnet will be assigned to a network + Allocated, // The subnet is in use. + Free // The subnet is ready to be allocated. + } + + long getPrefixId(); + + String getSubnet(); + + Long getNetworkId(); + + State getState(); +} diff --git a/api/src/main/java/com/cloud/network/Ipv6Service.java b/api/src/main/java/com/cloud/network/Ipv6Service.java new file mode 100644 index 00000000000..43978696298 --- /dev/null +++ b/api/src/main/java/com/cloud/network/Ipv6Service.java @@ -0,0 +1,94 @@ +// 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 java.util.List; + +import org.apache.cloudstack.api.command.user.ipv6.CreateIpv6FirewallRuleCmd; +import org.apache.cloudstack.api.command.user.ipv6.ListIpv6FirewallRulesCmd; +import org.apache.cloudstack.api.command.user.ipv6.UpdateIpv6FirewallRuleCmd; +import org.apache.cloudstack.api.response.VpcResponse; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; + +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterGuestIpv6Prefix; +import com.cloud.dc.Vlan; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.vpc.Vpc; +import com.cloud.utils.Pair; +import com.cloud.utils.component.PluggableService; +import com.cloud.vm.Nic; +import com.cloud.vm.NicProfile; + +public interface Ipv6Service extends PluggableService, Configurable { + + public static final int IPV6_SLAAC_CIDR_NETMASK = 64; + + static final ConfigKey Ipv6OfferingCreationEnabled = new ConfigKey("Advanced", Boolean.class, + "ipv6.offering.enabled", + "false", + "Indicates whether creation of IPv6 network/VPC offering is enabled or not.", + true); + + static final ConfigKey Ipv6PrefixSubnetCleanupInterval = new ConfigKey("Advanced", Integer.class, + "network.ipv6.prefix.subnet.cleanup.interval", + "1800", + "Determines how long (in seconds) to wait before deallocating prefix subnets which are in Allocating state. The default value = 1800 seconds.", + true); + + Pair getUsedTotalIpv6SubnetForPrefix(DataCenterGuestIpv6Prefix prefix); + + Pair getUsedTotalIpv6SubnetForZone(long zoneId); + + Pair preAllocateIpv6SubnetForNetwork(long zoneId) throws ResourceAllocationException; + + void assignIpv6SubnetToNetwork(String subnet, long networkId); + + void releaseIpv6SubnetForNetwork(long networkId); + + List getAllocatedIpv6FromVlanRange(Vlan vlan); + + Nic assignPublicIpv6ToNetwork(Network network, Nic nic); + + void updateNicIpv6(NicProfile nic, DataCenter dc, Network network) throws InsufficientAddressCapacityException; + + void releasePublicIpv6ForNic(Network network, String nicIpv6Address); + + List getPublicIpv6AddressesForNetwork(Network network); + + void updateIpv6RoutesForVpcResponse(Vpc vpc, VpcResponse response); + + void checkNetworkIpv6Upgrade(Network network) throws InsufficientAddressCapacityException, ResourceAllocationException; + + FirewallRule updateIpv6FirewallRule(UpdateIpv6FirewallRuleCmd updateIpv6FirewallRuleCmd); + + Pair,Integer> listIpv6FirewallRules(ListIpv6FirewallRulesCmd listIpv6FirewallRulesCmd); + + boolean revokeIpv6FirewallRule(Long id); + + FirewallRule createIpv6FirewallRule(CreateIpv6FirewallRuleCmd createIpv6FirewallRuleCmd) throws NetworkRuleConflictException; + + FirewallRule getIpv6FirewallRule(Long entityId); + + boolean applyIpv6FirewallRule(long id); + + void removePublicIpv6PlaceholderNics(Network network); +} diff --git a/api/src/main/java/com/cloud/network/Network.java b/api/src/main/java/com/cloud/network/Network.java index cdbdf6b4a72..49408b9110d 100644 --- a/api/src/main/java/com/cloud/network/Network.java +++ b/api/src/main/java/com/cloud/network/Network.java @@ -22,16 +22,15 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import com.cloud.exception.InvalidParameterValueException; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang.builder.ToStringBuilder; -import org.apache.commons.lang.builder.ToStringStyle; - import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.Displayable; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.apache.commons.lang3.StringUtils; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.Networks.Mode; import com.cloud.network.Networks.TrafficType; @@ -79,6 +78,22 @@ public interface Network extends ControlledEntity, StateObject, I } } + enum Routing { + Static, Dynamic; + + public static Routing fromValue(String type) { + if (StringUtils.isBlank(type)) { + return null; + } else if (type.equalsIgnoreCase("Static")) { + return Static; + } else if (type.equalsIgnoreCase("Dynamic")) { + return Dynamic; + } else { + throw new InvalidParameterValueException("Unexpected Routing type : " + type); + } + } + } + String updatingInSequence = "updatingInSequence"; String hideIpAddressUsage = "hideIpAddressUsage"; diff --git a/api/src/main/java/com/cloud/network/rules/FirewallRule.java b/api/src/main/java/com/cloud/network/rules/FirewallRule.java index 6a46c7da03d..369c6aa57eb 100644 --- a/api/src/main/java/com/cloud/network/rules/FirewallRule.java +++ b/api/src/main/java/com/cloud/network/rules/FirewallRule.java @@ -25,7 +25,7 @@ import org.apache.cloudstack.api.InternalIdentity; public interface FirewallRule extends ControlledEntity, Identity, InternalIdentity, Displayable { enum Purpose { - Firewall, PortForwarding, LoadBalancing, Vpn, StaticNat, NetworkACL, + Firewall, PortForwarding, LoadBalancing, Vpn, StaticNat, NetworkACL, Ipv6Firewall, } enum FirewallRuleType { diff --git a/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java b/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java index 42d82726786..5cccd6c5a82 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java @@ -25,6 +25,7 @@ import org.apache.cloudstack.api.command.admin.vpc.UpdateVPCOfferingCmd; import org.apache.cloudstack.api.command.user.vpc.ListVPCOfferingsCmd; import com.cloud.utils.Pair; +import com.cloud.utils.net.NetUtils; public interface VpcProvisioningService { @@ -34,7 +35,7 @@ public interface VpcProvisioningService { VpcOffering createVpcOffering(String name, String displayText, List supportedServices, Map> serviceProviders, - Map serviceCapabilitystList, + Map serviceCapabilitystList, NetUtils.InternetProtocol internetProtocol, Long serviceOfferingId, List domainIds, List zoneIds, VpcOffering.State state); Pair,Integer> listVpcOfferings(ListVPCOfferingsCmd cmd); diff --git a/api/src/main/java/com/cloud/offering/NetworkOffering.java b/api/src/main/java/com/cloud/offering/NetworkOffering.java index eaff1721634..bf904419176 100644 --- a/api/src/main/java/com/cloud/offering/NetworkOffering.java +++ b/api/src/main/java/com/cloud/offering/NetworkOffering.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.offering; +import java.util.Date; + import org.apache.cloudstack.acl.InfrastructureEntity; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; @@ -23,8 +25,6 @@ import org.apache.cloudstack.api.InternalIdentity; import com.cloud.network.Network.GuestType; import com.cloud.network.Networks.TrafficType; -import java.util.Date; - /** * Describes network offering * @@ -40,7 +40,7 @@ public interface NetworkOffering extends InfrastructureEntity, InternalIdentity, } public enum Detail { - InternalLbProvider, PublicLbProvider, servicepackageuuid, servicepackagedescription, PromiscuousMode, MacAddressChanges, ForgedTransmits, MacLearning, RelatedNetworkOffering, domainid, zoneid, pvlanType + InternalLbProvider, PublicLbProvider, servicepackageuuid, servicepackagedescription, PromiscuousMode, MacAddressChanges, ForgedTransmits, MacLearning, RelatedNetworkOffering, domainid, zoneid, pvlanType, internetProtocol } public final static String SystemPublicNetwork = "System-Public-Network"; diff --git a/api/src/main/java/org/apache/cloudstack/alert/AlertService.java b/api/src/main/java/org/apache/cloudstack/alert/AlertService.java index c2cd1b22332..ff176d8a82e 100644 --- a/api/src/main/java/org/apache/cloudstack/alert/AlertService.java +++ b/api/src/main/java/org/apache/cloudstack/alert/AlertService.java @@ -42,6 +42,7 @@ public interface AlertService { public static final AlertType ALERT_TYPE_STORAGE_ALLOCATED = new AlertType(Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED, "ALERT.STORAGE.ALLOCATED", true); public static final AlertType ALERT_TYPE_VIRTUAL_NETWORK_PUBLIC_IP = new AlertType(Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_PUBLIC_IP, "ALERT.NETWORK.PUBLICIP", true); + public static final AlertType ALERT_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET = new AlertType(Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET, "ALERT.NETWORK.IPV6SUBNET", true); public static final AlertType ALERT_TYPE_PRIVATE_IP = new AlertType(Capacity.CAPACITY_TYPE_PRIVATE_IP, "ALERT.NETWORK.PRIVATEIP", true); public static final AlertType ALERT_TYPE_SECONDARY_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_SECONDARY_STORAGE, "ALERT.STORAGE.SECONDARY", true); public static final AlertType ALERT_TYPE_HOST = new AlertType((short)7, "ALERT.COMPUTE.HOST", true); diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index db8eaf808d1..49d035610a6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -38,6 +38,7 @@ public class ApiConstants { public static final String LIST_LB_VMIPS = "lbvmips"; public static final String LIVE_PATCH = "livepatch"; public static final String AVAILABLE = "available"; + public static final String AVAILABLE_SUBNETS = "availablesubnets"; public static final String BACKUP_ID = "backupid"; public static final String BACKUP_OFFERING_NAME = "backupofferingname"; public static final String BACKUP_OFFERING_ID = "backupofferingid"; @@ -113,6 +114,7 @@ public class ApiConstants { public static final String HYPERVISOR_SNAPSHOT_RESERVE = "hypervisorsnapshotreserve"; public static final String DATADISK_OFFERING_LIST = "datadiskofferinglist"; public static final String DESCRIPTION = "description"; + public static final String DESTINATION = "destination"; public static final String DESTINATION_ZONE_ID = "destzoneid"; public static final String DETAILS = "details"; public static final String DEVICE_ID = "deviceid"; @@ -205,12 +207,14 @@ public class ApiConstants { public static final String ID = "id"; public static final String IDS = "ids"; public static final String INDEX = "index"; + public static final String PREFIX = "prefix"; public static final String PREVIOUS_ACL_RULE_ID = "previousaclruleid"; public static final String NEXT_ACL_RULE_ID = "nextaclruleid"; public static final String MOVE_ACL_CONSISTENCY_HASH = "aclconsistencyhash"; public static final String IMAGE_PATH = "imagepath"; public static final String INTERNAL_DNS1 = "internaldns1"; public static final String INTERNAL_DNS2 = "internaldns2"; + public static final String INTERNET_PROTOCOL = "internetprotocol"; public static final String INTERVAL_TYPE = "intervaltype"; public static final String LOCATION_TYPE = "locationtype"; public static final String IOPS_READ_RATE = "iopsreadrate"; @@ -238,6 +242,7 @@ public class ApiConstants { public static final String IS_READY = "isready"; public static final String IS_RECURSIVE = "isrecursive"; public static final String ISO_FILTER = "isofilter"; + public static final String ISO_ID = "isoid"; public static final String ISO_GUEST_OS_NONE = "None"; public static final String JAVA_DISTRIBUTION = "javadistribution"; public static final String JAVA_VERSION = "javaversion"; @@ -385,6 +390,7 @@ public class ApiConstants { public static final String STORAGE_POLICY = "storagepolicy"; public static final String STORAGE_MOTION_ENABLED = "storagemotionenabled"; public static final String STORAGE_CAPABILITIES = "storagecapabilities"; + public static final String SUBNET = "subnet"; public static final String OWNER = "owner"; public static final String SWAP_OWNER = "swapowner"; public static final String SYSTEM_VM_TYPE = "systemvmtype"; @@ -395,15 +401,16 @@ public class ApiConstants { public static final String TEMPLATE_ID = "templateid"; public static final String TEMPLATE_IDS = "templateids"; public static final String TEMPLATE_NAME = "templatename"; - public static final String ISO_ID = "isoid"; public static final String TIMEOUT = "timeout"; public static final String TIMEZONE = "timezone"; public static final String TIMEZONEOFFSET = "timezoneoffset"; + public static final String TOTAL_SUBNETS = "totalsubnets"; public static final String TYPE = "type"; public static final String TRUST_STORE = "truststore"; public static final String TRUST_STORE_PASSWORD = "truststorepass"; public static final String URL = "url"; public static final String USAGE_INTERFACE = "usageinterface"; + public static final String USED_SUBNETS = "usedsubnets"; public static final String USER_DATA = "userdata"; public static final String USER_FILTER = "userfilter"; public static final String USER_ID = "userid"; @@ -560,6 +567,7 @@ public class ApiConstants { public static final String SECURITY_GROUP_EANBLED = "securitygroupenabled"; public static final String LOCAL_STORAGE_ENABLED = "localstorageenabled"; public static final String GUEST_IP_TYPE = "guestiptype"; + public static final String GUEST_IP6_PREFIX = "guestip6prefix"; public static final String XENSERVER_NETWORK_LABEL = "xennetworklabel"; public static final String KVM_NETWORK_LABEL = "kvmnetworklabel"; public static final String VMWARE_NETWORK_LABEL = "vmwarenetworklabel"; @@ -602,6 +610,8 @@ public class ApiConstants { public static final String KEYWORD = "keyword"; public static final String LIST_ALL = "listall"; public static final String IP_RANGES = "ipranges"; + public static final String IPV6_ROUTING = "ip6routing"; + public static final String IPV6_ROUTES = "ip6routes"; public static final String SPECIFY_IP_RANGES = "specifyipranges"; public static final String IS_SOURCE_NAT = "issourcenat"; public static final String IS_STATIC_NAT = "isstaticnat"; diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java index da381b05e73..4d297cffe48 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java @@ -50,6 +50,7 @@ import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.NetworkRuleConflictException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Ipv6Service; import com.cloud.network.NetworkModel; import com.cloud.network.NetworkService; import com.cloud.network.NetworkUsageService; @@ -207,6 +208,8 @@ public abstract class BaseCmd { public AnnotationService annotationService; @Inject public ResourceIconManager resourceIconManager; + @Inject + public Ipv6Service ipv6Service; public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException; diff --git a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java index cdf375523de..ecf203a3472 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -22,20 +22,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import com.cloud.server.ResourceIcon; -import com.cloud.utils.Pair; -import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse; -import org.apache.cloudstack.api.response.ResourceIconResponse; -import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse; -import org.apache.cloudstack.api.response.GuestVlanResponse; -import org.apache.cloudstack.api.response.NetworkPermissionsResponse; -import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse; -import com.cloud.resource.RollingMaintenanceManager; -import org.apache.cloudstack.api.response.RollingMaintenanceResponse; -import org.apache.cloudstack.direct.download.DirectDownloadCertificate; -import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap; -import org.apache.cloudstack.direct.download.DirectDownloadManager; -import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants.HostDetails; @@ -49,6 +35,7 @@ import org.apache.cloudstack.api.response.AutoScalePolicyResponse; import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse; import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.CapacityResponse; import org.apache.cloudstack.api.response.ClusterResponse; @@ -56,6 +43,9 @@ import org.apache.cloudstack.api.response.ConditionResponse; import org.apache.cloudstack.api.response.ConfigurationResponse; import org.apache.cloudstack.api.response.CounterResponse; import org.apache.cloudstack.api.response.CreateCmdResponse; +import org.apache.cloudstack.api.response.DataCenterGuestIpv6PrefixResponse; +import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse; +import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; @@ -67,6 +57,7 @@ import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse; import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.GuestOsMappingResponse; import org.apache.cloudstack.api.response.GuestVlanRangeResponse; +import org.apache.cloudstack.api.response.GuestVlanResponse; import org.apache.cloudstack.api.response.HostForMigrationResponse; import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.HypervisorCapabilitiesResponse; @@ -84,6 +75,7 @@ import org.apache.cloudstack.api.response.ManagementServerResponse; import org.apache.cloudstack.api.response.NetworkACLItemResponse; import org.apache.cloudstack.api.response.NetworkACLResponse; import org.apache.cloudstack.api.response.NetworkOfferingResponse; +import org.apache.cloudstack.api.response.NetworkPermissionsResponse; import org.apache.cloudstack.api.response.NetworkResponse; import org.apache.cloudstack.api.response.NicResponse; import org.apache.cloudstack.api.response.NicSecondaryIpResponse; @@ -100,8 +92,11 @@ import org.apache.cloudstack.api.response.ProviderResponse; import org.apache.cloudstack.api.response.RegionResponse; import org.apache.cloudstack.api.response.RemoteAccessVpnResponse; import org.apache.cloudstack.api.response.ResourceCountResponse; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.ResourceLimitResponse; import org.apache.cloudstack.api.response.ResourceTagResponse; +import org.apache.cloudstack.api.response.RollingMaintenanceResponse; +import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse; import org.apache.cloudstack.api.response.SSHKeyPairResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; @@ -125,7 +120,6 @@ import org.apache.cloudstack.api.response.UpgradeRouterTemplateResponse; import org.apache.cloudstack.api.response.UsageRecordResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.VMSnapshotResponse; import org.apache.cloudstack.api.response.VirtualRouterProviderResponse; import org.apache.cloudstack.api.response.VlanIpRangeResponse; @@ -134,10 +128,14 @@ import org.apache.cloudstack.api.response.VpcOfferingResponse; import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.api.response.VpnUsersResponse; import org.apache.cloudstack.api.response.ZoneResponse; -import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.direct.download.DirectDownloadCertificate; +import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap; +import org.apache.cloudstack.direct.download.DirectDownloadManager; +import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; import org.apache.cloudstack.region.PortableIp; import org.apache.cloudstack.region.PortableIpRange; @@ -148,6 +146,7 @@ import com.cloud.capacity.Capacity; import com.cloud.configuration.ResourceCount; import com.cloud.configuration.ResourceLimit; import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterGuestIpv6Prefix; import com.cloud.dc.Pod; import com.cloud.dc.StorageNetworkIpRange; import com.cloud.dc.Vlan; @@ -201,6 +200,8 @@ import com.cloud.projects.Project; import com.cloud.projects.ProjectAccount; import com.cloud.projects.ProjectInvitation; import com.cloud.region.ha.GlobalLoadBalancerRule; +import com.cloud.resource.RollingMaintenanceManager; +import com.cloud.server.ResourceIcon; import com.cloud.server.ResourceTag; import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOSHypervisor; @@ -216,6 +217,7 @@ import com.cloud.user.SSHKeyPair; import com.cloud.user.User; import com.cloud.user.UserAccount; import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; import com.cloud.utils.net.Ip; import com.cloud.vm.InstanceGroup; import com.cloud.vm.Nic; @@ -284,6 +286,8 @@ public interface ResponseGenerator { ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities, Boolean showResourceIcon); + DataCenterGuestIpv6PrefixResponse createDataCenterGuestIpv6PrefixResponse(DataCenterGuestIpv6Prefix prefix); + VolumeResponse createVolumeResponse(ResponseView view, Volume volume); InstanceGroupResponse createInstanceGroupResponse(InstanceGroup group); @@ -512,4 +516,6 @@ public interface ResponseGenerator { DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateHostStatusResponse(DirectDownloadManager.HostCertificateStatus status); DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId, Long hostId, Pair result); + + FirewallResponse createIpv6FirewallRuleResponse(FirewallRule acl); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateGuestNetworkIpv6PrefixCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateGuestNetworkIpv6PrefixCmd.java new file mode 100644 index 00000000000..4f69be1b417 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateGuestNetworkIpv6PrefixCmd.java @@ -0,0 +1,115 @@ +// 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 org.apache.cloudstack.api.command.admin.network; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DataCenterGuestIpv6PrefixResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.log4j.Logger; + +import com.cloud.dc.DataCenterGuestIpv6Prefix; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; + +@APICommand(name = CreateGuestNetworkIpv6PrefixCmd.APINAME, + description = "Creates a guest network IPv6 prefix.", + responseObject = DataCenterGuestIpv6PrefixResponse.class, + since = "4.17.0.0", + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + authorized = {RoleType.Admin}) +public class CreateGuestNetworkIpv6PrefixCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(CreateGuestNetworkIpv6PrefixCmd.class); + + public static final String APINAME = "createGuestNetworkIpv6Prefix"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + required = true, + description = "UUID of zone to which the IPv6 prefix belongs to.", + validations = {ApiArgValidator.PositiveNumber}) + private Long zoneId; + + @Parameter(name = ApiConstants.PREFIX, + type = CommandType.STRING, + required = true, + description = "The /56 or higher IPv6 CIDR for network prefix.") + private String prefix; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + + public Long getZoneId() { + return zoneId; + } + + public String getPrefix() { + return prefix; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_GUEST_IP6_PREFIX_CREATE; + } + + @Override + public String getEventDescription() { + return "Creating guest IPv6 prefix " + getPrefix() + " for zone=" + getZoneId(); + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, + ResourceAllocationException { + DataCenterGuestIpv6Prefix result = _configService.createDataCenterGuestIpv6Prefix(this); + if (result != null) { + DataCenterGuestIpv6PrefixResponse response = _responseGenerator.createDataCenterGuestIpv6PrefixResponse(result); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create zone guest IPv6 prefix."); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseAsyncCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateManagementNetworkIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateManagementNetworkIpRangeCmd.java index 3d4013c8bc7..54c191a1be6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateManagementNetworkIpRangeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateManagementNetworkIpRangeCmd.java @@ -89,7 +89,7 @@ public class CreateManagementNetworkIpRangeCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.VLAN, type = CommandType.STRING, - description = "Optional. The vlan id the ip range sits on, default to Null when it is not specificed which means you network is not on any Vlan") + description = "Optional. The vlan id the ip range sits on, default to Null when it is not specified which means you network is not on any Vlan") private String vlan; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java index 7172633f5f9..eb8e9c6ec8e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java @@ -92,6 +92,12 @@ public class CreateNetworkOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.GUEST_IP_TYPE, type = CommandType.STRING, required = true, description = "guest type of the network offering: Shared or Isolated") private String guestIptype; + @Parameter(name = ApiConstants.INTERNET_PROTOCOL, + type = CommandType.STRING, + description = "The internet protocol of network offering. Options are ipv4 and dualstack. Default is ipv4. dualstack will create a network offering that supports both IPv4 and IPv6", + since = "4.17.0") + private String internetProtocol; + @Parameter(name = ApiConstants.SUPPORTED_SERVICES, type = CommandType.LIST, collectionType = CommandType.STRING, @@ -211,6 +217,10 @@ public class CreateNetworkOfferingCmd extends BaseCmd { return guestIptype; } + public String getInternetProtocol() { + return internetProtocol; + } + public Boolean getSpecifyIpRanges() { return specifyIpRanges == null ? false : specifyIpRanges; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteGuestNetworkIpv6PrefixCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteGuestNetworkIpv6PrefixCmd.java new file mode 100644 index 00000000000..830aeac8dae --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteGuestNetworkIpv6PrefixCmd.java @@ -0,0 +1,101 @@ +// 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 org.apache.cloudstack.api.command.admin.network; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DataCenterGuestIpv6PrefixResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.log4j.Logger; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import com.cloud.utils.exception.CloudRuntimeException; + +@APICommand(name = DeleteGuestNetworkIpv6PrefixCmd.APINAME, + description = "Deletes an existing guest network IPv6 prefix.", + responseObject = SuccessResponse.class, + since = "4.17.0.0", + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + authorized = {RoleType.Admin}) +public class DeleteGuestNetworkIpv6PrefixCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(DeleteGuestNetworkIpv6PrefixCmd.class); + public static final String APINAME = "deleteGuestNetworkIpv6Prefix"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DataCenterGuestIpv6PrefixResponse.class, required = true, description = "Id of the guest network IPv6 prefix") + private Long id; + + public Long getId() { + return id; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_GUEST_IP6_PREFIX_DELETE; + } + + @Override + public String getEventDescription() { + return "Deleting guest IPv6 prefix " + getId(); + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, + ResourceAllocationException { + try { + boolean result = _configService.deleteDataCenterGuestIpv6Prefix(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete guest network IPv6 prefix:" + getId()); + } + } catch (InvalidParameterValueException ex) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, ex.getMessage()); + } catch (CloudRuntimeException ex) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } + + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListGuestNetworkIpv6PrefixesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListGuestNetworkIpv6PrefixesCmd.java new file mode 100644 index 00000000000..d31a3ba3351 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListGuestNetworkIpv6PrefixesCmd.java @@ -0,0 +1,88 @@ +// 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 org.apache.cloudstack.api.command.admin.network; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.DataCenterGuestIpv6PrefixResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ZoneResponse; + +import com.cloud.dc.DataCenterGuestIpv6Prefix; + +@APICommand(name = ListGuestNetworkIpv6PrefixesCmd.APINAME, + description = "Lists guest network IPv6 prefixes", + responseObject = DataCenterGuestIpv6PrefixResponse.class, + since = "4.17.0", + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) + +public class ListGuestNetworkIpv6PrefixesCmd extends BaseListCmd { + + public static final String APINAME = "listGuestNetworkIpv6Prefixes"; + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = DataCenterGuestIpv6PrefixResponse.class, + description = "UUID of the IPv6 prefix.") + private Long id; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + description = "UUID of zone to which the IPv6 prefix belongs to.") + private Long zoneId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public Long getZoneId() { + return zoneId; + } + + @Override + public void execute() { + List prefixes = _configService.listDataCenterGuestIpv6Prefixes(this); + ListResponse response = new ListResponse<>(); + List prefixResponses = new ArrayList<>(); + for (DataCenterGuestIpv6Prefix prefix : prefixes) { + DataCenterGuestIpv6PrefixResponse prefixResponse = _responseGenerator.createDataCenterGuestIpv6PrefixResponse(prefix); + prefixResponse.setObjectName("guestnetworkipv6prefix"); + prefixResponses.add(prefixResponse); + } + + response.setResponses(prefixResponses, prefixes.size()); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java index 2f4949d7b11..6539c5c226d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java @@ -72,7 +72,13 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { private Map> serviceProviderList; @Parameter(name = ApiConstants.SERVICE_CAPABILITY_LIST, type = CommandType.MAP, description = "desired service capabilities as part of vpc offering", since = "4.4") - private Map> serviceCapabilitystList; + private Map> serviceCapabilityList; + + @Parameter(name = ApiConstants.INTERNET_PROTOCOL, + type = CommandType.STRING, + description = "The internet protocol of the offering. Options are ipv4 and dualstack. Default is ipv4. dualstack will create an offering that supports both IPv4 and IPv6", + since = "4.17.0") + private String internetProtocol; @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, type = CommandType.UUID, @@ -145,8 +151,12 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { return serviceProviderMap; } - public Map> getServiceCapabilitystList() { - return serviceCapabilitystList; + public Map> getServiceCapabilityList() { + return serviceCapabilityList; + } + + public String getInternetProtocol() { + return internetProtocol; } public Long getServiceOfferingId() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java new file mode 100644 index 00000000000..780baa5664d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java @@ -0,0 +1,255 @@ +// 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 org.apache.cloudstack.api.command.user.ipv6; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.FirewallResponse; +import org.apache.cloudstack.api.response.FirewallRuleResponse; +import org.apache.cloudstack.api.response.NetworkResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.rules.FirewallRule; +import com.cloud.user.Account; +import com.cloud.utils.net.NetUtils; + +@APICommand(name = CreateIpv6FirewallRuleCmd.APINAME, description = "Creates an Ipv6 firewall rule in the given network (the network has to belong to VPC)", responseObject = FirewallRuleResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class CreateIpv6FirewallRuleCmd extends BaseAsyncCreateCmd { + public static final Logger s_logger = Logger.getLogger(CreateIpv6FirewallRuleCmd.class.getName()); + + public static final String APINAME = "createIpv6FirewallRule"; + + // /////////////////////////////////////////////////// + // ////////////// API parameters ///////////////////// + // /////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, required = true, description = "the protocol for the Ipv6 firewall rule. Valid values are TCP/UDP/ICMP/ALL or valid protocol number") + private String protocol; + + @Parameter(name = ApiConstants.START_PORT, type = CommandType.INTEGER, description = "the starting port of Ipv6 firewall rule") + private Integer publicStartPort; + + @Parameter(name = ApiConstants.END_PORT, type = CommandType.INTEGER, description = "the ending port of Ipv6 firewall rule") + private Integer publicEndPort; + + @Parameter(name = ApiConstants.CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, description = "the source CIDR list to allow traffic from. Multiple entries must be separated by a single comma character (,).") + private List sourceCidrList; + + @Parameter(name = ApiConstants.DEST_CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, description = "the destination CIDR list to allow traffic to. Multiple entries must be separated by a single comma character (,).") + private List destinationCidrlist; + + @Parameter(name = ApiConstants.ICMP_TYPE, type = CommandType.INTEGER, description = "type of the ICMP message being sent") + private Integer icmpType; + + @Parameter(name = ApiConstants.ICMP_CODE, type = CommandType.INTEGER, description = "error code for this ICMP message") + private Integer icmpCode; + + @Parameter(name = ApiConstants.NETWORK_ID, type = CommandType.UUID, entityType = NetworkResponse.class, description = "The network of the VM the Ipv6 firewall rule will be created for", required = true) + private Long networkId; + + @Parameter(name = ApiConstants.TRAFFIC_TYPE, type = CommandType.STRING, description = "the traffic type for the Ipv6 firewall rule, can be ingress or egress, defaulted to ingress if not specified") + private String trafficType; + + @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "an optional field, whether to the display the rule to the end user or not", authorized = {RoleType.Admin}) + private Boolean display; + + // /////////////////////////////////////////////////// + // ///////////////// Accessors /////////////////////// + // /////////////////////////////////////////////////// + + @Override + public boolean isDisplay() { + if (display != null) { + return display; + } else { + return true; + } + } + + public String getProtocol() { + String p = protocol == null ? "" : protocol.trim(); + if (StringUtils.isNumeric(p)) { + int protoNumber = Integer.parseInt(p); + switch (protoNumber) { + case 1: + p = NetUtils.ICMP_PROTO; + break; + case 6: + p = NetUtils.TCP_PROTO; + break; + case 17: + p = NetUtils.UDP_PROTO; + break; + default: + throw new InvalidParameterValueException(String.format("Protocol %d not supported", protoNumber)); + + } + } + return p; + } + + public List getSourceCidrList() { + if (sourceCidrList != null) { + return sourceCidrList; + } else { + List oneCidrList = new ArrayList(); + oneCidrList.add(NetUtils.ALL_IP6_CIDRS); + return oneCidrList; + } + } + + public List getDestinationCidrList() { + if (destinationCidrlist != null) { + return destinationCidrlist; + } else { + List oneCidrList = new ArrayList(); + oneCidrList.add(NetUtils.ALL_IP6_CIDRS); + return oneCidrList; + } + } + + public FirewallRule.TrafficType getTrafficType() { + if (trafficType == null) { + return FirewallRule.TrafficType.Ingress; + } + for (FirewallRule.TrafficType type : FirewallRule.TrafficType.values()) { + if (type.toString().equalsIgnoreCase(trafficType)) { + return type; + } + } + throw new InvalidParameterValueException("Invalid traffic type " + trafficType); + } + + // /////////////////////////////////////////////////// + // ///////////// API Implementation/////////////////// + // /////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + public Integer getSourcePortStart() { + return publicStartPort; + } + + public Integer getSourcePortEnd() { + if (publicEndPort == null) { + if (publicStartPort != null) { + return publicStartPort; + } + } else { + return publicEndPort; + } + + return null; + } + + public Long getNetworkId() { + return networkId; + } + + @Override + public long getEntityOwnerId() { + Network network = _networkService.getNetwork(networkId); + if (network != null) { + return network.getAccountId(); + } + Account owner = CallContext.current().getCallingAccount(); + return owner.getAccountId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_IPV6_FIREWALL_RULE_CREATE; + } + + @Override + public String getEventDescription() { + return "Creating ipv6 firewall rule"; + } + + public Integer getIcmpCode() { + if (icmpCode != null) { + return icmpCode; + } else if (getProtocol().equalsIgnoreCase(NetUtils.ICMP_PROTO)) { + return -1; + } + return null; + } + + public Integer getIcmpType() { + if (icmpType != null) { + return icmpType; + } else if (getProtocol().equalsIgnoreCase(NetUtils.ICMP_PROTO)) { + return -1; + + } + return null; + } + + @Override + public void create() { + try { + FirewallRule result = ipv6Service.createIpv6FirewallRule(this); + setEntityId(result.getId()); + setEntityUuid(result.getUuid()); + } catch (NetworkRuleConflictException e) { + s_logger.trace("Network Rule Conflict: ", e); + throw new ServerApiException(ApiErrorCode.NETWORK_RULE_CONFLICT_ERROR, e.getMessage(), e); + } + } + + @Override + public void execute() throws ResourceUnavailableException { + boolean success = false; + FirewallRule rule = ipv6Service.getIpv6FirewallRule(getEntityId()); + try { + CallContext.current().setEventDetails("Rule ID: " + getEntityId()); + success = ipv6Service.applyIpv6FirewallRule(rule.getId()); + + // State is different after the rule is applied, so get new object here + rule = ipv6Service.getIpv6FirewallRule(getEntityId()); + FirewallResponse ruleResponse = new FirewallResponse(); + if (rule != null) { + ruleResponse = _responseGenerator.createIpv6FirewallRuleResponse(rule); + setResponseObject(ruleResponse); + } + ruleResponse.setResponseName(getCommandName()); + } finally { + if (!success || rule == null) { + ipv6Service.revokeIpv6FirewallRule(getEntityId()); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create ipv6 firewall rule"); + } + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/DeleteIpv6FirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/DeleteIpv6FirewallRuleCmd.java new file mode 100644 index 00000000000..04c6082e05a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/DeleteIpv6FirewallRuleCmd.java @@ -0,0 +1,97 @@ +// 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 org.apache.cloudstack.api.command.user.ipv6; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.FirewallRuleResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.rules.FirewallRule; +import com.cloud.user.Account; + +@APICommand(name = DeleteIpv6FirewallRuleCmd.APINAME, description = "Deletes a IPv6 firewall rule", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class DeleteIpv6FirewallRuleCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(DeleteIpv6FirewallRuleCmd.class.getName()); + public static final String APINAME = "deleteIpv6FirewallRule"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = FirewallRuleResponse.class, required = true, description = "the ID of the IPv6 firewall rule") + private Long id; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_IPV6_FIREWALL_RULE_DELETE; + } + + @Override + public String getEventDescription() { + return ("Deleting IPv6 firewall rule ID=" + id); + } + + @Override + public long getEntityOwnerId() { + FirewallRule rule = _firewallService.getFirewallRule(id); + if (rule != null) { + return rule.getAccountId(); + } + Account caller = CallContext.current().getCallingAccount(); + return caller.getAccountId(); + } + + @Override + public void execute() throws ResourceUnavailableException { + CallContext.current().setEventDetails("IPv6 firewall rule ID: " + id); + boolean result = ipv6Service.revokeIpv6FirewallRule(id); + + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete IPv6 firewall rule"); + } + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/ListIpv6FirewallRulesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/ListIpv6FirewallRulesCmd.java new file mode 100644 index 00000000000..3f91f523edc --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/ListIpv6FirewallRulesCmd.java @@ -0,0 +1,118 @@ +// 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 org.apache.cloudstack.api.command.user.ipv6; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListTaggedResourcesCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.user.firewall.IListFirewallRulesCmd; +import org.apache.cloudstack.api.response.FirewallResponse; +import org.apache.cloudstack.api.response.FirewallRuleResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.NetworkResponse; +import org.apache.log4j.Logger; + +import com.cloud.network.rules.FirewallRule; +import com.cloud.utils.Pair; + +@APICommand(name = ListIpv6FirewallRulesCmd.APINAME, description = "Lists all IPv6 firewall rules", responseObject = FirewallRuleResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListIpv6FirewallRulesCmd extends BaseListTaggedResourcesCmd implements IListFirewallRulesCmd { + public static final Logger s_logger = Logger.getLogger(ListIpv6FirewallRulesCmd.class.getName()); + + public static final String APINAME = "listIpv6FirewallRules"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = FirewallRuleResponse.class, + description = "Lists ipv6 firewall rule with the specified ID") + private Long id; + + @Parameter(name = ApiConstants.NETWORK_ID, type = CommandType.UUID, entityType = NetworkResponse.class, description = "list ipv6 firewall rules by network ID") + private Long networkId; + + @Parameter(name = ApiConstants.TRAFFIC_TYPE, type = CommandType.STRING, description = "list ipv6 firewall rules by traffic type - ingress or egress") + private String trafficType; + + @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "list resources by display flag; only ROOT admin is eligible to pass this parameter", authorized = {RoleType.Admin}) + private Boolean display; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + @Override + public Long getNetworkId() { + return networkId; + } + + @Override + public Long getId() { + return id; + } + + @Override + public FirewallRule.TrafficType getTrafficType() { + if (trafficType != null) { + return FirewallRule.TrafficType.valueOf(trafficType); + } + return null; + } + + @Override + public Long getIpAddressId() { + return null; + } + + @Override + public Boolean getDisplay() { + if (display != null) { + return display; + } + return super.getDisplay(); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + @Override + public void execute() { + Pair, Integer> result = ipv6Service.listIpv6FirewallRules(this); + ListResponse response = new ListResponse<>(); + List ruleResponses = new ArrayList<>(); + + for (FirewallRule rule : result.first()) { + FirewallResponse ruleData = _responseGenerator.createIpv6FirewallRuleResponse(rule); + ruleResponses.add(ruleData); + } + response.setResponses(ruleResponses, result.second()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/UpdateIpv6FirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/UpdateIpv6FirewallRuleCmd.java new file mode 100644 index 00000000000..bb8fc71b553 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/UpdateIpv6FirewallRuleCmd.java @@ -0,0 +1,175 @@ +// 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 org.apache.cloudstack.api.command.user.ipv6; + +import java.util.List; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseAsyncCustomIdCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.FirewallResponse; +import org.apache.cloudstack.api.response.FirewallRuleResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.rules.FirewallRule; +import com.cloud.user.Account; + +@APICommand(name = UpdateIpv6FirewallRuleCmd.APINAME, description = "Updates Ipv6 firewall rule with specified ID", responseObject = FirewallRuleResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class UpdateIpv6FirewallRuleCmd extends BaseAsyncCustomIdCmd { + public static final Logger s_logger = Logger.getLogger(UpdateIpv6FirewallRuleCmd.class.getName()); + + public static final String APINAME = "updateIpv6FirewallRule"; + + // /////////////////////////////////////////////////// + // ////////////// API parameters ///////////////////// + // /////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = FirewallRuleResponse.class, required = true, description = "the ID of the ipv6 firewall rule") + private Long id; + + @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, description = "the protocol for the Ipv6 firewall rule. Valid values are TCP/UDP/ICMP/ALL or valid protocol number") + private String protocol; + + @Parameter(name = ApiConstants.START_PORT, type = CommandType.INTEGER, description = "the starting port of Ipv6 firewall rule") + private Integer publicStartPort; + + @Parameter(name = ApiConstants.END_PORT, type = CommandType.INTEGER, description = "the ending port of Ipv6 firewall rule") + private Integer publicEndPort; + + @Parameter(name = ApiConstants.CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, description = "the cidr list to allow traffic from/to. Multiple entries must be separated by a single comma character (,).") + private List cidrlist; + + @Parameter(name = ApiConstants.ICMP_TYPE, type = CommandType.INTEGER, description = "type of the ICMP message being sent") + private Integer icmpType; + + @Parameter(name = ApiConstants.ICMP_CODE, type = CommandType.INTEGER, description = "error code for this ICMP message") + private Integer icmpCode; + + @Parameter(name = ApiConstants.TRAFFIC_TYPE, type = CommandType.STRING, description = "the traffic type for the Ipv6 firewall rule, can be Ingress or Egress, defaulted to Ingress if not specified") + private String trafficType; + + @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "an optional field, whether to the display the Ipv6 firewall rule to the end user or not", since = "4.4", authorized = { + RoleType.Admin}) + private Boolean display; + + // /////////////////////////////////////////////////// + // ///////////////// Accessors /////////////////////// + // /////////////////////////////////////////////////// + + @Override + public boolean isDisplay() { + if (display != null) { + return display; + } else { + return true; + } + } + + public Long getId() { + return id; + } + + public String getProtocol() { + if (protocol != null) { + return protocol.trim(); + } else { + return null; + } + } + + public List getSourceCidrList() { + return cidrlist; + } + + public FirewallRule.TrafficType getTrafficType() { + if (trafficType != null) { + for (FirewallRule.TrafficType type : FirewallRule.TrafficType.values()) { + if (type.toString().equalsIgnoreCase(trafficType)) { + return type; + } + } + } + return null; + } + + // /////////////////////////////////////////////////// + // ///////////// API Implementation/////////////////// + // /////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + RESPONSE_SUFFIX; + } + + public Integer getSourcePortStart() { + return publicStartPort; + } + + public Integer getSourcePortEnd() { + return publicEndPort; + } + + @Override + public long getEntityOwnerId() { + FirewallRule rule = _firewallService.getFirewallRule(id); + if (rule != null) { + return rule.getAccountId(); + } + Account caller = CallContext.current().getCallingAccount(); + return caller.getAccountId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_IPV6_FIREWALL_RULE_UPDATE; + } + + @Override + public String getEventDescription() { + return "Updating ipv6 firewall rule"; + } + + public Integer getIcmpCode() { + return icmpCode; + } + + public Integer getIcmpType() { + return icmpType; + } + + @Override + public void execute() throws ResourceUnavailableException { + CallContext.current().setEventDetails("Rule Id: " + getId()); + FirewallRule rules = ipv6Service.updateIpv6FirewallRule(this); + FirewallResponse ruleResponse = _responseGenerator.createIpv6FirewallRuleResponse(rules); + setResponseObject(ruleResponse); + ruleResponse.setResponseName(getCommandName()); + } + + @Override + public void checkUuid() { + if (this.getCustomId() != null) { + _uuidMgr.checkUuid(this.getCustomId(), FirewallRule.class); + } + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLCmd.java index 7fd75719b93..28416ef9793 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLCmd.java @@ -122,6 +122,7 @@ public class CreateNetworkACLCmd extends BaseAsyncCreateCmd { } else { List oneCidrList = new ArrayList(); oneCidrList.add(NetUtils.ALL_IP4_CIDRS); + oneCidrList.add(NetUtils.ALL_IP6_CIDRS); return oneCidrList; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DataCenterGuestIpv6PrefixResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DataCenterGuestIpv6PrefixResponse.java new file mode 100644 index 00000000000..fd0803f1c58 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/DataCenterGuestIpv6PrefixResponse.java @@ -0,0 +1,87 @@ +// 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 org.apache.cloudstack.api.response; + +import java.util.Date; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import com.cloud.dc.DataCenterGuestIpv6Prefix; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = DataCenterGuestIpv6Prefix.class) +public class DataCenterGuestIpv6PrefixResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "id of the guest IPv6 prefix") + private String id; + + @SerializedName(ApiConstants.PREFIX) + @Param(description = "guest IPv6 prefix") + private String prefix; + + @SerializedName(ApiConstants.ZONE_ID) + @Param(description = "id of zone to which the IPv6 prefix belongs to." ) + private String zoneId; + + @SerializedName(ApiConstants.USED_SUBNETS) + @Param(description = "count of the used IPv6 subnets for the prefix." ) + private Integer usedSubnets; + + @SerializedName(ApiConstants.AVAILABLE_SUBNETS) + @Param(description = "count of the available IPv6 subnets for the prefix." ) + private Integer availableSubnets; + + @SerializedName(ApiConstants.TOTAL_SUBNETS) + @Param(description = "count of the total IPv6 subnets for the prefix." ) + private Integer totalSubnets; + + @SerializedName(ApiConstants.CREATED) + @Param(description = " date when this IPv6 prefix was created." ) + private Date created; + + public void setId(String id) { + this.id = id; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public void setUsedSubnets(Integer usedSubnets) { + this.usedSubnets = usedSubnets; + } + + public void setAvailableSubnets(Integer availableSubnets) { + this.availableSubnets = availableSubnets; + } + + public void setTotalSubnets(Integer totalSubnets) { + this.totalSubnets = totalSubnets; + } + + public void setCreated(Date created) { + this.created = created; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/FirewallResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/FirewallResponse.java index 3ab4472737f..a9ecdd8e339 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/FirewallResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/FirewallResponse.java @@ -83,6 +83,10 @@ public class FirewallResponse extends BaseResponse { @Param(description = "the cidr list to forward traffic to. Multiple entries are separated by a single comma character (,).") private String destCidr; + @SerializedName(ApiConstants.TRAFFIC_TYPE) + @Param(description = "the traffic type for the firewall rule", since = "4.17.0") + private String trafficType; + public void setId(String id) { this.id = id; } @@ -138,4 +142,8 @@ public class FirewallResponse extends BaseResponse { public void setDestCidr(String cidrList){ this.destCidr = cidrList; } + + public void setTrafficType(String trafficType) { + this.trafficType = trafficType; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/IpRangeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/IpRangeResponse.java index c1d3061bce3..364c19f2df0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/IpRangeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/IpRangeResponse.java @@ -16,16 +16,23 @@ // under the License. package org.apache.cloudstack.api.response; -import com.google.gson.annotations.SerializedName; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; @SuppressWarnings("unused") public class IpRangeResponse extends BaseResponse { + @SerializedName(ApiConstants.GATEWAY) + @Param(description = "the gateway for the range") + private String gateway; + + @SerializedName(ApiConstants.CIDR) + @Param(description = "the CIDR for the range") + private String cidr; + @SerializedName(ApiConstants.START_IP) @Param(description = "the starting IP for the range") private String startIp; @@ -42,6 +49,22 @@ public class IpRangeResponse extends BaseResponse { @Param(description = "indicates Vlan ID for the range") private String vlanId; + public String getGateway() { + return gateway; + } + + public void setGateway(String gateway) { + this.gateway = gateway; + } + + public String getCidr() { + return cidr; + } + + public void setCidr(String cidr) { + this.cidr = cidr; + } + public String getStartIp() { return startIp; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/Ipv6RouteResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/Ipv6RouteResponse.java new file mode 100644 index 00000000000..4111ed94d5b --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/Ipv6RouteResponse.java @@ -0,0 +1,56 @@ +// 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 org.apache.cloudstack.api.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class Ipv6RouteResponse extends BaseResponse { + + @SerializedName(ApiConstants.SUBNET) + @Param(description = "the guest IPv6 cidr for route") + private String subnet; + + @SerializedName(ApiConstants.GATEWAY) + @Param(description = "the outbound IPv6 gateway") + private String gateway; + + public Ipv6RouteResponse(String subnet, String gateway) { + this.subnet = subnet; + this.gateway = gateway; + } + + public String getSubnet() { + return subnet; + } + + public void setSubnet(String subnet) { + this.subnet = subnet; + } + + public String getGateway() { + return gateway; + } + + public void setGateway(String gateway) { + this.gateway = gateway; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NetworkOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NetworkOfferingResponse.java index a3e979ec952..17fe1e0d741 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NetworkOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NetworkOfferingResponse.java @@ -139,6 +139,10 @@ public class NetworkOfferingResponse extends BaseResponseWithAnnotations { @Param(description = "the zone name(s) this disk offering belongs to. Ignore this information as it is not currently applicable.", since = "4.13.0") private String zone; + @SerializedName(ApiConstants.INTERNET_PROTOCOL) + @Param(description = "the internet protocol of the network offering") + private String internetProtocol; + public void setId(String id) { this.id = id; } @@ -262,4 +266,12 @@ public class NetworkOfferingResponse extends BaseResponseWithAnnotations { public void setZone(String zone) { this.zone = zone; } + + public String getInternetProtocol() { + return internetProtocol; + } + + public void setInternetProtocol(String internetProtocol) { + this.internetProtocol = internetProtocol; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java index 79750a322cf..7bed68f99b4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java @@ -275,6 +275,20 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement @Param(description = "true if guest network default egress policy is allow; false if default egress policy is deny") private Boolean egressDefaultPolicy; + @SerializedName(ApiConstants.INTERNET_PROTOCOL) + @Param(description = "The internet protocol of network offering") + private String internetProtocol; + + @SerializedName(ApiConstants.IPV6_ROUTING) + @Param(description = "The routing mode of network offering", since = "4.17.0") + private String ipv6Routing; + + @SerializedName(ApiConstants.IPV6_ROUTES) + @Param(description = "The routes for the network to ease adding route in upstream router", since = "4.17.0") + private Set ipv6Routes; + + public NetworkResponse() {} + public Boolean getDisplayNetwork() { return displayNetwork; } @@ -556,4 +570,20 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement public void setEgressDefaultPolicy(Boolean egressDefaultPolicy) { this.egressDefaultPolicy = egressDefaultPolicy; } + + public void setInternetProtocol(String internetProtocol) { + this.internetProtocol = internetProtocol; + } + + public void setIpv6Routing(String ipv6Routing) { + this.ipv6Routing = ipv6Routing; + } + + public void setIpv6Routes(Set ipv6Routes) { + this.ipv6Routes = ipv6Routes; + } + + public void addIpv6Route(Ipv6RouteResponse ipv6Route) { + this.ipv6Routes.add(ipv6Route); + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java index 5656a92955d..a22e2eb7024 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java @@ -16,14 +16,13 @@ // under the License. package org.apache.cloudstack.api.response; -import com.google.gson.annotations.SerializedName; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; import com.cloud.dc.Vlan; import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; @EntityReference(value = Vlan.class) @SuppressWarnings("unused") @@ -64,6 +63,10 @@ public class VlanIpRangeResponse extends BaseResponse implements ControlledEntit @Param(description = "the Pod name for the VLAN IP range") private String podName; + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "the description of the VLAN IP range") + private String description; + @SerializedName(ApiConstants.GATEWAY) @Param(description = "the gateway of the VLAN IP range") private String gateway; @@ -72,9 +75,9 @@ public class VlanIpRangeResponse extends BaseResponse implements ControlledEntit @Param(description = "the netmask of the VLAN IP range") private String netmask; - @SerializedName(ApiConstants.DESCRIPTION) - @Param(description = "the description of the VLAN IP range") - private String description; + @SerializedName(ApiConstants.CIDR) + @Param(description = "the cidr of the VLAN IP range") + private String cidr; @SerializedName(ApiConstants.START_IP) @Param(description = "the start ip of the VLAN IP range") @@ -167,6 +170,10 @@ public class VlanIpRangeResponse extends BaseResponse implements ControlledEntit this.podName = podName; } + public void setDescription(String description) { + this.description = description; + } + public void setGateway(String gateway) { this.gateway = gateway; } @@ -175,8 +182,8 @@ public class VlanIpRangeResponse extends BaseResponse implements ControlledEntit this.netmask = netmask; } - public void setDescription(String description) { - this.description = description; + public void setCidr(String cidr) { + this.cidr = cidr; } public void setStartIp(String startIp) { diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java index b8483c3e3d2..6881969646b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java @@ -82,6 +82,10 @@ public class VpcOfferingResponse extends BaseResponse { @Param(description = "the zone name(s) this disk offering belongs to. Ignore this information as it is not currently applicable.", since = "4.13.0") private String zone; + @SerializedName(ApiConstants.INTERNET_PROTOCOL) + @Param(description = "the internet protocol of the vpc offering") + private String internetProtocol; + public void setId(String id) { this.id = id; } @@ -149,4 +153,12 @@ public class VpcOfferingResponse extends BaseResponse { public void setZone(String zone) { this.zone = zone; } + + public String getInternetProtocol() { + return internetProtocol; + } + + public void setInternetProtocol(String internetProtocol) { + this.internetProtocol = internetProtocol; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java index 151c3a9561b..eb468cbbbd4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.api.response; import java.util.Date; import java.util.List; +import java.util.Set; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ApiConstants; @@ -131,6 +132,10 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0") ResourceIconResponse icon; + @SerializedName(ApiConstants.IPV6_ROUTES) + @Param(description = "The routes for the network to ease adding route in upstream router", since = "4.17.0") + private Set ipv6Routes; + public void setId(final String id) { this.id = id; } @@ -244,4 +249,8 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll public void setResourceIconResponse(ResourceIconResponse icon) { this.icon = icon; } + + public void setIpv6Routes(Set ipv6Routes) { + this.ipv6Routes = ipv6Routes; + } } diff --git a/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsType.java b/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsType.java index 0e3a1dad2c6..d3c15c8d5a3 100644 --- a/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsType.java +++ b/api/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsType.java @@ -19,7 +19,7 @@ package org.apache.cloudstack.diagnostics; public enum DiagnosticsType { - PING("ping"), TRACEROUTE("traceroute"), ARPING("arping"); + PING("ping"), PING6("ping6"), TRACEROUTE("traceroute"), TRACEROUTE6("traceroute6"), ARPING("arping"); private String value; diff --git a/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java b/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java index 0f53daa7e98..e9781993239 100644 --- a/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java +++ b/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java @@ -27,9 +27,14 @@ public class SetupGuestNetworkCommand extends NetworkElementCommand { String networkDomain; String defaultDns1 = null; String defaultDns2 = null; + String defaultIp6Dns1 = null; + String defaultIp6Dns2 = null; boolean isRedundant = false; boolean add = true; NicTO nic; + String routerIpv6 = null; + String routerIpv6Gateway = null; + String routerIpv6Cidr = null; public NicTO getNic() { return nic; @@ -43,6 +48,14 @@ public class SetupGuestNetworkCommand extends NetworkElementCommand { return defaultDns2; } + public String getDefaultIp6Dns1() { + return defaultIp6Dns1; + } + + public String getDefaultIp6Dns2() { + return defaultIp6Dns2; + } + public String getNetworkDomain() { return networkDomain; } @@ -69,4 +82,36 @@ public class SetupGuestNetworkCommand extends NetworkElementCommand { this.add = add; this.nic = nic; } + + public String getRouterIpv6() { + return routerIpv6; + } + + public void setRouterIpv6(String routerIpv6) { + this.routerIpv6 = routerIpv6; + } + + public String getRouterIpv6Gateway() { + return routerIpv6Gateway; + } + + public void setRouterIpv6Gateway(String routerIpv6Gateway) { + this.routerIpv6Gateway = routerIpv6Gateway; + } + + public String getRouterIpv6Cidr() { + return routerIpv6Cidr; + } + + public void setRouterIpv6Cidr(String routerIpv6Cidr) { + this.routerIpv6Cidr = routerIpv6Cidr; + } + + public void setDefaultIp6Dns1(String defaultIp6Dns1) { + this.defaultIp6Dns1 = defaultIp6Dns1; + } + + public void setDefaultIp6Dns2(String defaultIp6Dns2) { + this.defaultIp6Dns2 = defaultIp6Dns2; + } } diff --git a/core/src/main/java/com/cloud/agent/api/routing/SetIpv6FirewallRulesAnswer.java b/core/src/main/java/com/cloud/agent/api/routing/SetIpv6FirewallRulesAnswer.java new file mode 100644 index 00000000000..bb8584caf48 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/routing/SetIpv6FirewallRulesAnswer.java @@ -0,0 +1,39 @@ +// +// 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.agent.api.routing; + +import com.cloud.agent.api.Answer; + +public class SetIpv6FirewallRulesAnswer extends Answer { + String[] results; + + protected SetIpv6FirewallRulesAnswer() { + } + + public SetIpv6FirewallRulesAnswer(SetIpv6FirewallRulesCommand cmd, boolean success, String[] results) { + super(cmd, success, null); + assert (cmd.getRules().length == results.length) : "rules and their results should be the same length don't you think?"; + this.results = results; + } + + public String[] getResults() { + return results; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/routing/SetIpv6FirewallRulesCommand.java b/core/src/main/java/com/cloud/agent/api/routing/SetIpv6FirewallRulesCommand.java new file mode 100644 index 00000000000..62cc2cabaaa --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/routing/SetIpv6FirewallRulesCommand.java @@ -0,0 +1,49 @@ +// +// 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.agent.api.routing; + +import java.util.List; + +import com.cloud.agent.api.to.FirewallRuleTO; + +/** + * + * AccessDetails allow different components to put in information about + * how to access the components inside the command. + */ +public class SetIpv6FirewallRulesCommand extends NetworkElementCommand { + FirewallRuleTO[] rules; + + protected SetIpv6FirewallRulesCommand() { + } + + public SetIpv6FirewallRulesCommand(List rules) { + this.rules = rules.toArray(new FirewallRuleTO[rules.size()]); + } + + public FirewallRuleTO[] getRules() { + return rules; + } + + @Override + public int getAnswersCount() { + return rules.length; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/routing/SetNetworkACLCommand.java b/core/src/main/java/com/cloud/agent/api/routing/SetNetworkACLCommand.java index 5aa4fca4afa..064dac5f67a 100644 --- a/core/src/main/java/com/cloud/agent/api/routing/SetNetworkACLCommand.java +++ b/core/src/main/java/com/cloud/agent/api/routing/SetNetworkACLCommand.java @@ -26,8 +26,11 @@ import java.util.List; import com.cloud.agent.api.to.NetworkACLTO; import com.cloud.agent.api.to.NicTO; +import com.cloud.utils.net.NetUtils; public class SetNetworkACLCommand extends NetworkElementCommand { + public static final String RULE_DETAIL_SEPARATOR = ";"; + NetworkACLTO[] rules; NicTO nic; @@ -58,7 +61,8 @@ public class SetNetworkACLCommand extends NetworkElementCommand { if (aclTO.revoked() == true) { final StringBuilder sb = new StringBuilder(); /* This entry is added just to make sure atleast there will one entry in the list to get the ipaddress */ - sb.append(aclTO.getTrafficType().toString()).append(":reverted:0:0:0:"); + List revertRuleItems = Arrays.asList("", "reverted", "0", "0", "0", ""); + sb.append(aclTO.getTrafficType().toString()).append(String.join(RULE_DETAIL_SEPARATOR, revertRuleItems)); final String aclRuleEntry = sb.toString(); result[0][i++] = aclRuleEntry; continue; @@ -66,15 +70,15 @@ public class SetNetworkACLCommand extends NetworkElementCommand { List cidr; final StringBuilder sb = new StringBuilder(); - sb.append(aclTO.getTrafficType().toString()).append(":").append(aclTO.getProtocol()).append(":"); + sb.append(aclTO.getTrafficType().toString()).append(RULE_DETAIL_SEPARATOR).append(aclTO.getProtocol()).append(RULE_DETAIL_SEPARATOR); if ("icmp".compareTo(aclTO.getProtocol()) == 0) { - sb.append(aclTO.getIcmpType()).append(":").append(aclTO.getIcmpCode()).append(":"); + sb.append(aclTO.getIcmpType()).append(RULE_DETAIL_SEPARATOR).append(aclTO.getIcmpCode()).append(RULE_DETAIL_SEPARATOR); } else { - sb.append(aclTO.getStringPortRange()).append(":"); + sb.append(aclTO.getStringPortRange().replace(":", RULE_DETAIL_SEPARATOR)).append(RULE_DETAIL_SEPARATOR); } cidr = aclTO.getSourceCidrList(); if (cidr == null || cidr.isEmpty()) { - sb.append("0.0.0.0/0"); + sb.append(String.format("%s,%s", NetUtils.ALL_IP4_CIDRS, NetUtils.ALL_IP6_CIDRS)); } else { Boolean firstEntry = true; for (final String tag : cidr) { @@ -85,7 +89,7 @@ public class SetNetworkACLCommand extends NetworkElementCommand { firstEntry = false; } } - sb.append(":").append(aclTO.getAction()).append(":"); + sb.append(RULE_DETAIL_SEPARATOR).append(aclTO.getAction()).append(RULE_DETAIL_SEPARATOR); final String aclRuleEntry = sb.toString(); result[0][i++] = aclRuleEntry; } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java index a76d555b002..4450dfe66f2 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java @@ -31,6 +31,7 @@ public class VRScripts { public final static String VM_PASSWORD_CONFIG = "vm_password.json"; public static final String FORWARDING_RULES_CONFIG = "forwarding_rules.json"; public static final String FIREWALL_RULES_CONFIG = "firewall_rules.json"; + public static final String IPV6_FIREWALL_RULES_CONFIG = "ipv6_firewall_rules.json"; public static final String VPN_USER_LIST_CONFIG = "vpn_user_list.json"; public static final String STATICNAT_RULES_CONFIG = "staticnat_rules.json"; public static final String SITE_2_SITE_VPN_CONFIG = "site_2_site_vpn.json"; diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/AbstractConfigItemFacade.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/AbstractConfigItemFacade.java index 1042d23e7b0..bed472b6880 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/AbstractConfigItemFacade.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/AbstractConfigItemFacade.java @@ -23,6 +23,7 @@ import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.UUID; + import org.apache.log4j.Logger; import com.cloud.agent.api.BumpUpPriorityCommand; @@ -38,6 +39,7 @@ import com.cloud.agent.api.routing.NetworkElementCommand; import com.cloud.agent.api.routing.RemoteAccessVpnCfgCommand; import com.cloud.agent.api.routing.SavePasswordCommand; import com.cloud.agent.api.routing.SetFirewallRulesCommand; +import com.cloud.agent.api.routing.SetIpv6FirewallRulesCommand; import com.cloud.agent.api.routing.SetMonitorServiceCommand; import com.cloud.agent.api.routing.SetNetworkACLCommand; import com.cloud.agent.api.routing.SetPortForwardingRulesCommand; @@ -84,6 +86,7 @@ public abstract class AbstractConfigItemFacade { flyweight.put(DeleteIpAliasCommand.class, new DeleteIpAliasConfigItem()); flyweight.put(VmDataCommand.class, new VmDataConfigItem()); flyweight.put(SetFirewallRulesCommand.class, new SetFirewallRulesConfigItem()); + flyweight.put(SetIpv6FirewallRulesCommand.class, new SetIpv6FirewallRulesConfigItem()); flyweight.put(BumpUpPriorityCommand.class, new BumpUpPriorityConfigItem()); flyweight.put(RemoteAccessVpnCfgCommand.class, new RemoteAccessVpnConfigItem()); flyweight.put(VpnUsersCfgCommand.class, new VpnUsersConfigItem()); diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java index f96e6d47d2e..cb5dc4586c1 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java @@ -21,6 +21,8 @@ package com.cloud.agent.resource.virtualnetwork.facade; import java.util.List; +import org.apache.commons.lang3.StringUtils; + import com.cloud.agent.api.SetupGuestNetworkCommand; import com.cloud.agent.api.routing.NetworkElementCommand; import com.cloud.agent.api.to.NicTO; @@ -53,8 +55,28 @@ public class SetGuestNetworkConfigItem extends AbstractConfigItemFacade { } } + String dns6 = command.getDefaultIp6Dns1(); + if (StringUtils.isEmpty(dns6)) { + dns6 = command.getDefaultIp6Dns2(); + } else { + final String dns2 = command.getDefaultIp6Dns2(); + if (StringUtils.isNotEmpty(dns2)) { + dns6 += "," + dns2; + } + } + final GuestNetwork guestNetwork = new GuestNetwork(command.isAdd(), nic.getMac(), "eth" + nic.getDeviceId(), routerGIP, netmask, gateway, - cidr, dns, domainName); + cidr, dns, dns6, domainName); + guestNetwork.setRouterGuestIp6(nic.getIp6Address()); + guestNetwork.setRouterGuestIp6Gateway(nic.getIp6Gateway()); + guestNetwork.setRouterGuestIp6Cidr(nic.getIp6Cidr()); + if (nic.getIp6Cidr() != null) { + guestNetwork.setCidr6(String.valueOf(NetUtils.getIp6CidrSize(nic.getIp6Cidr()))); + } + + guestNetwork.setRouterIp6(command.getRouterIpv6()); + guestNetwork.setRouterIp6Gateway(command.getRouterIpv6Gateway()); + guestNetwork.setRouterIp6Cidr(command.getRouterIpv6Cidr()); return generateConfigItems(guestNetwork); } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetIpv6FirewallRulesConfigItem.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetIpv6FirewallRulesConfigItem.java new file mode 100644 index 00000000000..ff4f266f995 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetIpv6FirewallRulesConfigItem.java @@ -0,0 +1,59 @@ +// +// 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.agent.resource.virtualnetwork.facade; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.routing.SetIpv6FirewallRulesCommand; +import com.cloud.agent.api.to.FirewallRuleTO; +import com.cloud.agent.resource.virtualnetwork.ConfigItem; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.agent.resource.virtualnetwork.model.ConfigBase; +import com.cloud.agent.resource.virtualnetwork.model.FirewallRule; +import com.cloud.agent.resource.virtualnetwork.model.FirewallRules; + +public class SetIpv6FirewallRulesConfigItem extends AbstractConfigItemFacade{ + + @Override + public List generateConfig(final NetworkElementCommand cmd) { + final SetIpv6FirewallRulesCommand command = (SetIpv6FirewallRulesCommand) cmd; + + final List rules = new ArrayList(); + for (final FirewallRuleTO rule : command.getRules()) { + final FirewallRule fwRule = new FirewallRule(rule.getId(), rule.getSrcVlanTag(), rule.getSrcIp(), rule.getProtocol(), rule.getSrcPortRange(), rule.revoked(), + rule.isAlreadyAdded(), rule.getSourceCidrList(), rule.getDestCidrList(), rule.getPurpose().toString(), rule.getIcmpType(), rule.getIcmpCode(), rule.getTrafficType().toString(), + rule.getGuestCidr(), rule.isDefaultEgressPolicy()); + rules.add(fwRule); + } + + final FirewallRules ruleSet = new FirewallRules(rules.toArray(new FirewallRule[rules.size()])); + ruleSet.setType(ConfigBase.IPV6_FIREWALL_RULES); + return generateConfigItems(ruleSet); + } + + @Override + protected List generateConfigItems(final ConfigBase configuration) { + destinationFile = VRScripts.IPV6_FIREWALL_RULES_CONFIG; + + return super.generateConfigItems(configuration); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetNetworkAclConfigItem.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetNetworkAclConfigItem.java index 3eff242f440..a64328d516a 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetNetworkAclConfigItem.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetNetworkAclConfigItem.java @@ -60,7 +60,7 @@ public class SetNetworkAclConfigItem extends AbstractConfigItemFacade { for (int i = 0; i < aclRules.length; i++) { AclRule aclRule; - final String[] ruleParts = aclRules[i].split(":"); + final String[] ruleParts = aclRules[i].split(SetNetworkACLCommand.RULE_DETAIL_SEPARATOR); switch (ruleParts[1].toLowerCase()) { case "icmp": aclRule = new IcmpAclRule(ruleParts[4], "ACCEPT".equals(ruleParts[5]), Integer.parseInt(ruleParts[2]), Integer.parseInt(ruleParts[3])); @@ -94,7 +94,7 @@ public class SetNetworkAclConfigItem extends AbstractConfigItemFacade { final NetworkACL networkACL = new NetworkACL(dev, nic.getMac(), privateGw != null, nic.getIp(), netmask, ingressRules.toArray(new AclRule[ingressRules.size()]), egressRules.toArray(new AclRule[egressRules.size()])); - + networkACL.setNicIp6Cidr(nic.getIp6Cidr()); return generateConfigItems(networkACL); } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ConfigBase.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ConfigBase.java index 51424ea3115..ade80d71384 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ConfigBase.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ConfigBase.java @@ -29,6 +29,7 @@ public abstract class ConfigBase { public static final String VM_PASSWORD = "vmpassword"; public static final String FORWARDING_RULES = "forwardrules"; public static final String FIREWALL_RULES = "firewallrules"; + public static final String IPV6_FIREWALL_RULES = "ipv6firewallrules"; public static final String VPN_USER_LIST = "vpnuserlist"; public static final String STATICNAT_RULES = "staticnatrules"; public static final String IP_ALIAS_CONFIG = "ipaliases"; diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java index 076073e7949..6589c7806df 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java @@ -27,15 +27,23 @@ public class GuestNetwork extends ConfigBase { private String routerGuestNetmask; private String routerGuestGateway; private String cidr; + private String cidr6; private String dns; + private String dns6; private String domainName; + private String routerGuestIp6; + private String routerGuestIp6Gateway; + private String routerGuestIp6Cidr; + private String routerIp6; + private String routerIp6Gateway; + private String routerIp6Cidr; public GuestNetwork() { super(ConfigBase.GUEST_NETWORK); } public GuestNetwork(final boolean add, final String macAddress, final String device, final String routerGuestIp, final String routerGuestNetmask, final String routerGuestGateway, - final String cidr, final String dns, final String domainName) { + final String cidr, final String dns, final String dns6, final String domainName) { super(ConfigBase.GUEST_NETWORK); this.add = add; this.macAddress = macAddress; @@ -45,6 +53,7 @@ public class GuestNetwork extends ConfigBase { this.routerGuestGateway = routerGuestGateway; this.cidr = cidr; this.dns = dns; + this.dns6 = dns6; this.domainName = domainName; } @@ -104,6 +113,14 @@ public class GuestNetwork extends ConfigBase { this.cidr = cidr; } + public String getCidr6() { + return cidr6; + } + + public void setCidr6(String cidr6) { + this.cidr6 = cidr6; + } + public String getDns() { return dns; } @@ -112,6 +129,14 @@ public class GuestNetwork extends ConfigBase { this.dns = dns; } + public String getDns6() { + return dns6; + } + + public void setDns6(String dns6) { + this.dns6 = dns6; + } + public String getDomainName() { return domainName; } @@ -119,4 +144,52 @@ public class GuestNetwork extends ConfigBase { public void setDomainName(final String domainName) { this.domainName = domainName; } + + public String getRouterGuestIp6() { + return routerGuestIp6; + } + + public void setRouterGuestIp6(String routerGuestIp6) { + this.routerGuestIp6 = routerGuestIp6; + } + + public String getRouterGuestIp6Gateway() { + return routerGuestIp6Gateway; + } + + public void setRouterGuestIp6Gateway(String routerGuestIp6Gateway) { + this.routerGuestIp6Gateway = routerGuestIp6Gateway; + } + + public String getRouterGuestIp6Cidr() { + return routerGuestIp6Cidr; + } + + public void setRouterGuestIp6Cidr(String routerGuestIp6Cidr) { + this.routerGuestIp6Cidr = routerGuestIp6Cidr; + } + + public String getRouterIp6() { + return routerIp6; + } + + public void setRouterIp6(String routerIp6) { + this.routerIp6 = routerIp6; + } + + public String getRouterIp6Gateway() { + return routerIp6Gateway; + } + + public void setRouterIp6Gateway(String routerIp6Gateway) { + this.routerIp6Gateway = routerIp6Gateway; + } + + public String getRouterIp6Cidr() { + return routerIp6Cidr; + } + + public void setRouterIp6Cidr(String routerIp6Cidr) { + this.routerIp6Cidr = routerIp6Cidr; + } } \ No newline at end of file diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/NetworkACL.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/NetworkACL.java index 40a7d27a773..7481dd77de9 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/NetworkACL.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/NetworkACL.java @@ -25,6 +25,7 @@ public class NetworkACL extends ConfigBase { private boolean privateGatewayAcl; private String nicIp; private String nicNetmask; + private String nicIp6Cidr; private AclRule[] ingressRules; private AclRule[] egressRules; @@ -83,6 +84,14 @@ public class NetworkACL extends ConfigBase { this.nicNetmask = nicNetmask; } + public String getNicIp6Cidr() { + return nicIp6Cidr; + } + + public void setNicIp6Cidr(String nicIp6Cidr) { + this.nicIp6Cidr = nicIp6Cidr; + } + public AclRule[] getIngressRules() { return ingressRules; } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index bab36f4703b..b79cce5f604 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -277,6 +277,8 @@ public interface NetworkOrchestrationService { Nic savePlaceholderNic(Network network, String ip4Address, String ip6Address, Type vmType); + Nic savePlaceholderNic(Network network, String ip4Address, String ip6Address, String ip6Cidr, String ip6Gateway, String reserver, Type vmType); + DhcpServiceProvider getDhcpServiceProvider(Network network); DnsServiceProvider getDnsServiceProvider(Network network); diff --git a/engine/components-api/src/main/java/com/cloud/alert/AlertManager.java b/engine/components-api/src/main/java/com/cloud/alert/AlertManager.java index 6a90e74987f..2b7ffc9436b 100644 --- a/engine/components-api/src/main/java/com/cloud/alert/AlertManager.java +++ b/engine/components-api/src/main/java/com/cloud/alert/AlertManager.java @@ -41,6 +41,12 @@ public interface AlertManager extends Manager, AlertService { public static final ConfigKey AlertSmtpEnabledSecurityProtocols = new ConfigKey("Advanced", String.class, "alert.smtp.enabledSecurityProtocols", "", "White-space separated security protocols; ex: \"TLSv1 TLSv1.1\". Supported protocols: SSLv2Hello, SSLv3, TLSv1, TLSv1.1 and TLSv1.2", true); + public static final ConfigKey Ipv6SubnetCapacityThreshold = new ConfigKey("Advanced", Double.class, + "zone.virtualnetwork.ipv6subnet.capacity.notificationthreshold", + "0.75", + "Percentage (as a value between 0 and 1) of guest network IPv6 subnet utilization above which alerts will be sent.", + true); + void clearAlert(AlertType alertType, long dataCenterId, long podId); void recalculateCapacity(); diff --git a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java index 66772002b5f..0442cac86c8 100644 --- a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java +++ b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java @@ -42,6 +42,7 @@ import com.cloud.offering.NetworkOffering.Availability; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.org.Grouping.AllocationState; import com.cloud.user.Account; +import com.cloud.utils.net.NetUtils; /** * ConfigurationManager handles adding pods/zones, changing IP ranges, enabling external firewalls, and editing @@ -214,7 +215,8 @@ public interface ConfigurationManager { NetworkOfferingVO createNetworkOffering(String name, String displayText, TrafficType trafficType, String tags, boolean specifyVlan, Availability availability, Integer networkRate, Map> serviceProviderMap, boolean isDefault, Network.GuestType type, boolean systemOnly, Long serviceOfferingId, boolean conserveMode, Map> serviceCapabilityMap, boolean specifyIpRanges, boolean isPersistent, - Map details, boolean egressDefaultPolicy, Integer maxconn, boolean enableKeepAlive, Boolean forVpc, List domainIds, List zoneIds, boolean enableOffering); + Map details, boolean egressDefaultPolicy, Integer maxconn, boolean enableKeepAlive, Boolean forVpc, + List domainIds, List zoneIds, boolean enableOffering, final NetUtils.InternetProtocol internetProtocol); Vlan createVlanAndPublicIpRange(long zoneId, long networkId, long physicalNetworkId, boolean forVirtualNetwork, boolean forSystemVms, Long podId, String startIP, String endIP, String vlanGateway, String vlanNetmask, String vlanId, boolean bypassVlanOverlapCheck, Domain domain, Account vlanOwner, String startIPv6, String endIPv6, String vlanIp6Gateway, String vlanIp6Cidr) @@ -252,7 +254,7 @@ public interface ConfigurationManager { * @throws * @throws */ - Pod editPod(long id, String name, String startIp, String endIp, String gateway, String netmask, String allocationStateStr); + Pod editPod(long id, String name, String startIp, String endIp, String gateway, String netmask, String allocationState); void checkPodCidrSubnets(long zoneId, Long podIdToBeSkipped, String cidr); diff --git a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java index db4221f9e3e..56e70ca56f1 100644 --- a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java @@ -109,7 +109,7 @@ public interface VpcManager { Network createVpcGuestNetwork(long ntwkOffId, String name, String displayText, String gateway, String cidr, String vlanId, String networkDomain, Account owner, Long domainId, PhysicalNetwork pNtwk, long zoneId, ACLType aclType, Boolean subdomainAccess, long vpcId, Long aclId, Account caller, - Boolean displayNetworkEnabled, String externalId) + Boolean displayNetworkEnabled, String externalId, String ip6Gateway, String ip6Cidr) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index aeaecb42c6d..b47232bdcda 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -55,7 +55,9 @@ import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.PublishScope; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.network.dao.NetworkPermissionDao; -import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; @@ -116,6 +118,7 @@ import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.IpAddress; import com.cloud.network.IpAddressManager; +import com.cloud.network.Ipv6Service; import com.cloud.network.Network; import com.cloud.network.Network.Capability; import com.cloud.network.Network.Event; @@ -156,6 +159,7 @@ import com.cloud.network.dao.PhysicalNetworkTrafficTypeVO; import com.cloud.network.dao.PhysicalNetworkVO; import com.cloud.network.dao.RemoteAccessVpnDao; import com.cloud.network.dao.RemoteAccessVpnVO; +import com.cloud.network.dao.RouterNetworkDao; import com.cloud.network.element.AggregatedCommandExecutor; import com.cloud.network.element.ConfigDriveNetworkElement; import com.cloud.network.element.DhcpServiceProvider; @@ -169,6 +173,7 @@ import com.cloud.network.element.UserDataServiceProvider; import com.cloud.network.element.VirtualRouterElement; import com.cloud.network.guru.NetworkGuru; import com.cloud.network.guru.NetworkGuruAdditionalFunctions; +import com.cloud.network.guru.PublicNetworkGuru; import com.cloud.network.lb.LoadBalancingRulesManager; import com.cloud.network.router.VirtualRouter; import com.cloud.network.rules.FirewallManager; @@ -244,7 +249,7 @@ import com.cloud.vm.dao.NicSecondaryIpDao; import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; -import org.apache.commons.lang3.StringUtils; +import com.googlecode.ipv6.IPv6Address; /** * NetworkManagerImpl implements NetworkManager. @@ -324,6 +329,10 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra public ManagementServer mgr; @Inject NetworkPermissionDao networkPermissionDao; + @Inject + Ipv6Service ipv6Service; + @Inject + RouterNetworkDao routerNetworkDao; List networkGurus; @@ -482,21 +491,21 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra if (_networkOfferingDao.findByUniqueName(NetworkOffering.QuickCloudNoServices) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.QuickCloudNoServices, "Offering for QuickCloud with no services", TrafficType.Guest, null, true, Availability.Optional, null, new HashMap>(), true, Network.GuestType.Shared, false, null, true, null, true, - false, null, false, null, true, false, null, null, true); + false, null, false, null, true, false, null, null, true, null); } //#2 - SG enabled network offering if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultSharedNetworkOfferingWithSGService) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultSharedNetworkOfferingWithSGService, "Offering for Shared Security group enabled networks", TrafficType.Guest, null, true, Availability.Optional, null, defaultSharedNetworkOfferingProviders, true, Network.GuestType.Shared, false, null, true, - null, true, false, null, false, null, true, false, null, null, true); + null, true, false, null, false, null, true, false, null, null, true, null); } //#3 - shared network offering with no SG service if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultSharedNetworkOffering) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultSharedNetworkOffering, "Offering for Shared networks", TrafficType.Guest, null, true, Availability.Optional, null, defaultSharedNetworkOfferingProviders, true, Network.GuestType.Shared, false, null, true, null, true, false, null, false, - null, true, false, null, null, true); + null, true, false, null, null, true, null); } //#4 - default isolated offering with Source nat service @@ -504,14 +513,14 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingWithSourceNatService, "Offering for Isolated networks with Source Nat service enabled", TrafficType.Guest, null, false, Availability.Required, null, defaultIsolatedSourceNatEnabledNetworkOfferingProviders, true, Network.GuestType.Isolated, false, null, true, null, false, false, null, false, null, - true, false, null, null, true); + true, false, null, null, true, null); } //#5 - default vpc offering with LB service if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworks) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworks, "Offering for Isolated VPC networks with Source Nat service enabled", TrafficType.Guest, null, false, Availability.Optional, null, - defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, null, null, true); + defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, null, null, true, null); } //#6 - default vpc offering with no LB service @@ -520,14 +529,14 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra defaultVPCOffProviders.remove(Service.Lb); offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworksNoLB, "Offering for Isolated VPC networks with Source Nat service enabled and LB service disabled", TrafficType.Guest, null, false, Availability.Optional, - null, defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, null, null, true); + null, defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, null, null, true, null); } //#7 - isolated offering with source nat disabled if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultIsolatedNetworkOffering) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOffering, "Offering for Isolated networks with no Source Nat service", TrafficType.Guest, null, true, Availability.Optional, null, defaultIsolatedNetworkOfferingProviders, true, Network.GuestType.Isolated, false, null, - true, null, true, false, null, false, null, true, false, null, null, true); + true, null, true, false, null, false, null, true, false, null, null, true, null); } //#8 - network offering with internal lb service @@ -549,7 +558,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworksWithInternalLB) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworksWithInternalLB, "Offering for Isolated VPC networks with Internal Lb support", TrafficType.Guest, null, false, Availability.Optional, null, internalLbOffProviders, - true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, null, null, true); + true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, null, null, true, null); offering.setInternalLb(true); offering.setPublicLb(false); _networkOfferingDao.update(offering.getId(), offering); @@ -580,7 +589,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultSharedEIPandELBNetworkOffering) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultSharedEIPandELBNetworkOffering, "Offering for Shared networks with Elastic IP and Elastic LB capabilities", TrafficType.Guest, null, true, Availability.Optional, null, - netscalerServiceProviders, true, Network.GuestType.Shared, false, null, true, serviceCapabilityMap, true, false, null, false, null, true, false, null, null, true); + netscalerServiceProviders, true, Network.GuestType.Shared, false, null, true, serviceCapabilityMap, true, false, null, false, null, true, false, null, null, true, null); offering.setDedicatedLB(false); _networkOfferingDao.update(offering.getId(), offering); } @@ -2348,6 +2357,25 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } s_logger.debug("Removed nic id=" + nic.getId()); + // release assigned IPv6 for Isolated Network VR NIC + + if (Type.DomainRouter.equals(vm.getType()) && PublicNetworkGuru.class.getSimpleName().equals(nic.getReserver()) + && StringUtils.isNotEmpty(nic.getIPv6Address())) { + List routerNetworks = routerNetworkDao.getRouterNetworks(vm.getId()); + if (CollectionUtils.isNotEmpty(routerNetworks)) { + Network guestNetwork = _networksDao.findById(routerNetworks.get(0)); + ipv6Service.releasePublicIpv6ForNic(guestNetwork, nic.getIPv6Address()); + } + } + + if (Type.User.equals(vm.getType()) && GuestType.Isolated.equals(network.getGuestType()) + && _networkOfferingDao.isIpv6Supported(network.getNetworkOfferingId()) && StringUtils.isNotEmpty(nic.getIPv6Address())) { + final boolean usageHidden = networkDetailsDao.isNetworkUsageHidden(network.getId()); + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_IP6_RELEASE, network.getAccountId(), network.getDataCenterId(), 0L, + nic.getIPv6Address(), false, Vlan.VlanType.VirtualNetwork.toString(), false, usageHidden, + IPv6Address.class.getName(), null); + } + //remove the secondary ip addresses corresponding to to this nic if (!removeVmSecondaryIpsOfNic(nic.getId())) { s_logger.debug("Removing nic " + nic.getId() + " secondary ip addreses failed"); @@ -3125,6 +3153,9 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra throw new CloudRuntimeException("Failed to delete network " + networkFinal + "; was unable to cleanup corresponding ip ranges"); } else { // commit transaction only when ips and vlans for the network are released successfully + + ipv6Service.releaseIpv6SubnetForNetwork(networkId); + ipv6Service.removePublicIpv6PlaceholderNics(networkFinal); try { stateTransitTo(networkFinal, Event.DestroyNetwork); } catch (final NoTransitionException e) { @@ -4343,10 +4374,20 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Override public NicVO savePlaceholderNic(final Network network, final String ip4Address, final String ip6Address, final Type vmType) { + return savePlaceholderNic(network, ip4Address, ip6Address, null, null, null, vmType); + } + + @Override + public NicVO savePlaceholderNic(final Network network, final String ip4Address, final String ip6Address, final String ip6Cidr, final String ip6Gateway, final String reserver, final Type vmType) { final NicVO nic = new NicVO(null, null, network.getId(), null); nic.setIPv4Address(ip4Address); nic.setIPv6Address(ip6Address); + nic.setIPv6Cidr(ip6Cidr); + nic.setIPv6Gateway(ip6Gateway); nic.setReservationStrategy(ReservationStrategy.PlaceHolder); + if (reserver != null) { + nic.setReserver(reserver); + } nic.setState(Nic.State.Reserved); nic.setVmType(vmType); return _nicDao.persist(nic); diff --git a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java index aaae874182f..50c40134a91 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java +++ b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java @@ -241,6 +241,7 @@ public class CapacityVO implements Capacity { capacityNames.put(CAPACITY_TYPE_LOCAL_STORAGE, "LOCAL_STORAGE"); capacityNames.put(CAPACITY_TYPE_GPU, "GPU"); capacityNames.put(CAPACITY_TYPE_CPU_CORE, "CPU_CORE"); + capacityNames.put(CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET, "VIRTUAL_NETWORK_IPV6_SUBNET"); } public static String getCapacityName (Short capacityType) { diff --git a/engine/schema/src/main/java/com/cloud/dc/DataCenterGuestIpv6PrefixVO.java b/engine/schema/src/main/java/com/cloud/dc/DataCenterGuestIpv6PrefixVO.java new file mode 100644 index 00000000000..ff48510e4d2 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/dc/DataCenterGuestIpv6PrefixVO.java @@ -0,0 +1,99 @@ +// 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.dc; + +import java.util.Date; +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import com.cloud.utils.db.GenericDao; + +@Entity +@Table(name = "dc_ip6_guest_prefix") +public class DataCenterGuestIpv6PrefixVO implements DataCenterGuestIpv6Prefix { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + String uuid; + + @Column(name = "data_center_id") + private long dataCenterId; + + @Column(name = "prefix") + private String prefix; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + @Column(name= GenericDao.REMOVED_COLUMN) + private Date removed; + + public DataCenterGuestIpv6PrefixVO(long dcId, String prefix) { + this(); + this.dataCenterId = dcId; + this.prefix = prefix; + this.created = new Date(); + } + + protected DataCenterGuestIpv6PrefixVO() { + this.uuid = UUID.randomUUID().toString(); + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public Long getDataCenterId() { + return dataCenterId; + } + + public void setDataCenterId(long dcId) { + this.dataCenterId = dcId; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + @Override + public Date getCreated() { + return created; + } + + +} diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterGuestIpv6PrefixDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterGuestIpv6PrefixDao.java new file mode 100644 index 00000000000..2d8e5fcba00 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterGuestIpv6PrefixDao.java @@ -0,0 +1,28 @@ +// 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.dc.dao; + +import java.util.List; + +import com.cloud.dc.DataCenterGuestIpv6PrefixVO; +import com.cloud.utils.db.GenericDao; + +public interface DataCenterGuestIpv6PrefixDao extends GenericDao { + + List listByDataCenterId(long dcId); +} diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterGuestIpv6PrefixDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterGuestIpv6PrefixDaoImpl.java new file mode 100644 index 00000000000..8e8f79895e7 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterGuestIpv6PrefixDaoImpl.java @@ -0,0 +1,43 @@ +// 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.dc.dao; + +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.cloud.dc.DataCenterGuestIpv6PrefixVO; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Component +@DB +public class DataCenterGuestIpv6PrefixDaoImpl extends GenericDaoBase implements DataCenterGuestIpv6PrefixDao { + + public DataCenterGuestIpv6PrefixDaoImpl() { + } + + @Override + public List listByDataCenterId(long dcId) { + QueryBuilder sc = QueryBuilder.create(DataCenterGuestIpv6PrefixVO.class); + sc.and(sc.entity().getDataCenterId(), SearchCriteria.Op.EQ, dcId); + return sc.list(); + } +} diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java index a3e3c60210c..9a9f4b377d9 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java @@ -60,4 +60,8 @@ public interface VlanDao extends GenericDao { List listVlansByNetworkIdAndGateway(long networkid, String gateway); List listDedicatedVlans(long accountId); + + List listIpv6RangeByPhysicalNetworkIdAndVlanId(long physicalNetworkId, String vlanId); + + List listIpv6SupportingVlansByZone(long zoneId); } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java index 7b3ca130cd8..0ae9c0b1979 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java @@ -26,6 +26,8 @@ import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import com.cloud.dc.AccountVlanMapVO; @@ -62,6 +64,9 @@ public class VlanDaoImpl extends GenericDaoBase implements VlanDao protected SearchBuilder ZoneWideNonDedicatedVlanSearch; protected SearchBuilder VlanGatewaysearch; protected SearchBuilder DedicatedVlanSearch; + protected SearchBuilder PhysicalNetworkVlanIp6Search; + protected SearchBuilder ZoneIp6Search; + protected SearchBuilder ZoneVlansSearch; protected SearchBuilder AccountVlanMapSearch; protected SearchBuilder DomainVlanMapSearch; @@ -255,6 +260,23 @@ public class VlanDaoImpl extends GenericDaoBase implements VlanDao DedicatedVlanSearch.done(); AccountVlanMapSearch.done(); + PhysicalNetworkVlanIp6Search = createSearchBuilder(); + PhysicalNetworkVlanIp6Search.and("physicalNetworkId", PhysicalNetworkVlanIp6Search.entity().getPhysicalNetworkId(), SearchCriteria.Op.EQ); + PhysicalNetworkVlanIp6Search.and("vlanId", PhysicalNetworkVlanIp6Search.entity().getVlanTag(), SearchCriteria.Op.EQ); + PhysicalNetworkVlanIp6Search.and("ip6Gateway", PhysicalNetworkVlanIp6Search.entity().getIp6Gateway(), SearchCriteria.Op.NNULL); + PhysicalNetworkVlanIp6Search.and("ip6Cidr", PhysicalNetworkVlanIp6Search.entity().getIp6Cidr(), SearchCriteria.Op.NNULL); + PhysicalNetworkVlanIp6Search.done(); + + ZoneIp6Search = createSearchBuilder(); + ZoneIp6Search.and("zoneId", ZoneIp6Search.entity().getDataCenterId(), SearchCriteria.Op.EQ); + ZoneIp6Search.and("ip6Gateway", ZoneIp6Search.entity().getIp6Gateway(), SearchCriteria.Op.NNULL); + ZoneIp6Search.and("ip6Cidr", ZoneIp6Search.entity().getIp6Cidr(), SearchCriteria.Op.NNULL); + ZoneIp6Search.done(); + + ZoneVlansSearch = createSearchBuilder(); + ZoneVlansSearch.and("zoneId", ZoneVlansSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ); + ZoneVlansSearch.and("vlan", ZoneVlansSearch.entity().getVlanTag(), SearchCriteria.Op.IN); + ZoneVlansSearch.done(); return result; } @@ -387,4 +409,29 @@ public class VlanDaoImpl extends GenericDaoBase implements VlanDao return listBy(sc); } + @Override + public List listIpv6RangeByPhysicalNetworkIdAndVlanId(long physicalNetworkId, String vlanId) { + SearchCriteria sc = PhysicalNetworkVlanIp6Search.create(); + sc.setParameters("physicalNetworkId", physicalNetworkId); + if(StringUtils.isNotEmpty(vlanId)) { + sc.setParameters("vlanId", vlanId); + } + return listBy(sc); + } + + @Override + public List listIpv6SupportingVlansByZone(long zoneId) { + SearchCriteria sc = ZoneIp6Search.create(); + sc.setParameters("zoneId", zoneId); + List vlanVOS = listBy(sc); + Object[] vlanIds = vlanVOS.stream().map(VlanVO::getVlanTag).toArray(); + if (ArrayUtils.isEmpty(vlanIds)) { + return new ArrayList<>(); + } + sc = ZoneVlansSearch.create(); + sc.setParameters("zoneId", zoneId); + sc.setParameters("vlan", vlanIds); + return listBy(sc); + } + } diff --git a/engine/schema/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMapVO.java b/engine/schema/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMapVO.java new file mode 100644 index 00000000000..769965dfdf1 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMapVO.java @@ -0,0 +1,129 @@ +// 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 java.util.Date; +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import com.cloud.utils.db.GenericDao; + +@Entity +@Table(name = "ip6_guest_prefix_subnet_network_map") +public class Ipv6GuestPrefixSubnetNetworkMapVO implements Ipv6GuestPrefixSubnetNetworkMap { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "prefix_id") + private Long prefixId; + + @Column(name = "subnet") + private String subnet; + + @Column(name = "network_id") + private Long networkId; + + @Column(name = "state") + private State state; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "updated") + Date updated; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + @Column(name= GenericDao.REMOVED_COLUMN) + private Date removed; + + protected Ipv6GuestPrefixSubnetNetworkMapVO() { + uuid = UUID.randomUUID().toString(); + } + + protected Ipv6GuestPrefixSubnetNetworkMapVO(long prefixId, String subnet, Long networkId, Ipv6GuestPrefixSubnetNetworkMap.State state) { + this.prefixId = prefixId; + this.subnet = subnet; + this.networkId = networkId; + this.state = state; + uuid = UUID.randomUUID().toString(); + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public long getPrefixId() { + return prefixId; + } + + @Override + public String getSubnet() { + return subnet; + } + + @Override + public Long getNetworkId() { + return networkId; + } + + public void setNetworkId(Long networkId) { + this.networkId = networkId; + } + + @Override + public State getState() { + return state; + } + + public void setState(Ipv6GuestPrefixSubnetNetworkMap.State state) { + this.state = state; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } + + public Date getUpdated() { + return updated; + } + + public Date getCreated() { + return created; + } +} diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDao.java b/engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDao.java new file mode 100644 index 00000000000..23e6915a23f --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDao.java @@ -0,0 +1,33 @@ +// 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.dao; + +import java.util.List; + +import com.cloud.network.Ipv6GuestPrefixSubnetNetworkMap; +import com.cloud.network.Ipv6GuestPrefixSubnetNetworkMapVO; +import com.cloud.utils.db.GenericDao; + +public interface Ipv6GuestPrefixSubnetNetworkMapDao extends GenericDao { + List listUsedByPrefix(long prefixId); + Ipv6GuestPrefixSubnetNetworkMapVO findFirstAvailable(long prefixId); + Ipv6GuestPrefixSubnetNetworkMapVO findByNetworkId(long networkId); + Ipv6GuestPrefixSubnetNetworkMapVO findBySubnet(String subnet); + List findPrefixesInStates(Ipv6GuestPrefixSubnetNetworkMap.State... states); + void deleteByPrefixId(long prefixId); +} diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDaoImpl.java new file mode 100644 index 00000000000..fac6d4825fa --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDaoImpl.java @@ -0,0 +1,111 @@ +// 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.dao; + +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Component; + +import com.cloud.network.Ipv6GuestPrefixSubnetNetworkMap; +import com.cloud.network.Ipv6GuestPrefixSubnetNetworkMapVO; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Component +@DB +public class Ipv6GuestPrefixSubnetNetworkMapDaoImpl extends GenericDaoBase implements Ipv6GuestPrefixSubnetNetworkMapDao { + + protected SearchBuilder PrefixStateSearch; + protected SearchBuilder PrefixIdSearch; + protected SearchBuilder NetworkIdSearch; + protected SearchBuilder SubnetSearch; + protected SearchBuilder StatesSearch; + + @PostConstruct + public void init() { + PrefixStateSearch = createSearchBuilder(); + PrefixStateSearch.and("prefixId", PrefixStateSearch.entity().getPrefixId(), SearchCriteria.Op.EQ); + PrefixStateSearch.and("state", PrefixStateSearch.entity().getState(), SearchCriteria.Op.IN); + PrefixStateSearch.done(); + PrefixIdSearch = createSearchBuilder(); + PrefixIdSearch.and("prefixId", PrefixIdSearch.entity().getPrefixId(), SearchCriteria.Op.EQ); + PrefixIdSearch.done(); + NetworkIdSearch = createSearchBuilder(); + NetworkIdSearch.and("networkId", NetworkIdSearch.entity().getNetworkId(), SearchCriteria.Op.EQ); + NetworkIdSearch.done(); + SubnetSearch = createSearchBuilder(); + SubnetSearch.and("subnet", SubnetSearch.entity().getSubnet(), SearchCriteria.Op.EQ); + SubnetSearch.done(); + StatesSearch = createSearchBuilder(); + StatesSearch.and("state", StatesSearch.entity().getState(), SearchCriteria.Op.IN); + StatesSearch.done(); + } + + @Override + public List listUsedByPrefix(long prefixId) { + SearchCriteria sc = PrefixStateSearch.create(); + sc.setParameters("prefixId", prefixId); + sc.setParameters("state", (Object[]) new Ipv6GuestPrefixSubnetNetworkMap.State[]{Ipv6GuestPrefixSubnetNetworkMap.State.Allocated, Ipv6GuestPrefixSubnetNetworkMap.State.Allocating}); + Filter searchFilter = new Filter(Ipv6GuestPrefixSubnetNetworkMapVO.class, "id", true, null, 1L); + return listBy(sc, searchFilter); + } + + @Override + public Ipv6GuestPrefixSubnetNetworkMapVO findFirstAvailable(long prefixId) { + SearchCriteria sc = PrefixStateSearch.create(); + sc.setParameters("prefixId", prefixId); + sc.setParameters("state", (Object[]) new Ipv6GuestPrefixSubnetNetworkMap.State[]{Ipv6GuestPrefixSubnetNetworkMap.State.Free}); + Filter searchFilter = new Filter(Ipv6GuestPrefixSubnetNetworkMapVO.class, "id", true, null, 1L); + List list = listBy(sc, searchFilter); + return CollectionUtils.isNotEmpty(list) ? list.get(0) : null; + } + + @Override + public Ipv6GuestPrefixSubnetNetworkMapVO findByNetworkId(long networkId) { + SearchCriteria sc = NetworkIdSearch.create(); + sc.setParameters("networkId", networkId); + return findOneBy(sc); + } + + @Override + public Ipv6GuestPrefixSubnetNetworkMapVO findBySubnet(String subnet) { + SearchCriteria sc = SubnetSearch.create(); + sc.setParameters("subnet", subnet); + return findOneBy(sc); + } + + @Override + public List findPrefixesInStates(Ipv6GuestPrefixSubnetNetworkMap.State... states) { + SearchCriteria sc = StatesSearch.create(); + sc.setParameters("state", (Object[])states); + return listBy(sc); + } + + @Override + public void deleteByPrefixId(long prefixId) { + SearchCriteria sc = PrefixIdSearch.create(); + sc.setParameters("prefixId", prefixId); + remove(sc); + } +} diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailsDao.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailsDao.java index efe84f6bff2..f2d297fbd21 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailsDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailsDao.java @@ -21,5 +21,5 @@ import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; import com.cloud.utils.db.GenericDao; public interface NetworkDetailsDao extends GenericDao, ResourceDetailsDao { - + boolean isNetworkUsageHidden(long networkId); } \ No newline at end of file diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailsDaoImpl.java index 7fc8cdab0b0..d036b131900 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailsDaoImpl.java @@ -21,6 +21,8 @@ import org.springframework.stereotype.Component; import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; +import com.cloud.network.Network; + @Component public class NetworkDetailsDaoImpl extends ResourceDetailsDaoBase implements NetworkDetailsDao { @@ -29,4 +31,9 @@ public class NetworkDetailsDaoImpl extends ResourceDetailsDaoBase { /** @@ -28,4 +29,8 @@ public interface VpcOfferingDao extends GenericDao { * @return VpcOfferingVO */ VpcOfferingVO findByUniqueName(String uniqueName); + + NetUtils.InternetProtocol getVpcOfferingInternetProtocol(long offeringId); + + boolean isIpv6Supported(long offeringId); } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDaoImpl.java index 15a1cc4dc3a..1cc6a21da76 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDaoImpl.java @@ -17,6 +17,9 @@ package com.cloud.network.vpc.dao; +import javax.inject.Inject; + +import org.apache.cloudstack.api.ApiConstants; import org.springframework.stereotype.Component; import com.cloud.network.vpc.VpcOfferingVO; @@ -26,12 +29,16 @@ import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.net.NetUtils; @Component @DB() public class VpcOfferingDaoImpl extends GenericDaoBase implements VpcOfferingDao { final SearchBuilder AllFieldsSearch; + @Inject + VpcOfferingDetailsDao detailsDao; + protected VpcOfferingDaoImpl() { super(); @@ -65,4 +72,16 @@ public class VpcOfferingDaoImpl extends GenericDaoBase impl sc.setParameters("uName", uniqueName); return findOneBy(sc); } + + @Override + public NetUtils.InternetProtocol getVpcOfferingInternetProtocol(long offeringId) { + String internetProtocolStr = detailsDao.getDetail(offeringId, ApiConstants.INTERNET_PROTOCOL); + return NetUtils.InternetProtocol.fromValue(internetProtocolStr); + } + + @Override + public boolean isIpv6Supported(long offeringId) { + NetUtils.InternetProtocol internetProtocol = getVpcOfferingInternetProtocol(offeringId); + return NetUtils.InternetProtocol.isIpv6EnabledProtocol(internetProtocol); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDetailsDao.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDetailsDao.java index da5bf52f764..59abf5c780b 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDetailsDao.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDetailsDao.java @@ -27,4 +27,5 @@ import com.cloud.utils.db.GenericDao; public interface VpcOfferingDetailsDao extends GenericDao, ResourceDetailsDao { List findDomainIds(final long resourceId); List findZoneIds(final long resourceId); + String getDetail(long offeringId, String detailName); } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDetailsDaoImpl.java index 0feacd99160..2a23a3714ad 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDetailsDaoImpl.java @@ -22,10 +22,23 @@ import java.util.List; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; +import org.apache.commons.collections.CollectionUtils; import com.cloud.network.vpc.VpcOfferingDetailsVO; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; public class VpcOfferingDetailsDaoImpl extends ResourceDetailsDaoBase implements VpcOfferingDetailsDao { + private final SearchBuilder ValueSearch; + + public VpcOfferingDetailsDaoImpl() { + ValueSearch = createSearchBuilder(); + ValueSearch.select(null, SearchCriteria.Func.DISTINCT, ValueSearch.entity().getValue()); + ValueSearch.and("resourceId", ValueSearch.entity().getResourceId(), SearchCriteria.Op.EQ); + ValueSearch.and("name", ValueSearch.entity().getName(), SearchCriteria.Op.EQ); + ValueSearch.and("display", ValueSearch.entity().isDisplay(), SearchCriteria.Op.EQ); + ValueSearch.done(); + } @Override public void addDetail(long resourceId, String key, String value, boolean display) { @@ -55,4 +68,17 @@ public class VpcOfferingDetailsDaoImpl extends ResourceDetailsDaoBase sc = ValueSearch.create(); + sc.setParameters("name", detailName); + sc.setParameters("resourceId", offeringId); + List results = search(sc, null); + if (CollectionUtils.isEmpty(results)) { + return null; + } else { + return results.get(0).getValue(); + } + } } \ No newline at end of file diff --git a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDao.java b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDao.java index 238a191bd77..60d7701cd05 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDao.java +++ b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDao.java @@ -26,6 +26,7 @@ import com.cloud.offering.NetworkOffering.Availability; import com.cloud.offering.NetworkOffering.Detail; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.utils.db.GenericDao; +import com.cloud.utils.net.NetUtils; /** * NetworkOfferingDao deals with searches and operations done on the @@ -69,4 +70,8 @@ public interface NetworkOfferingDao extends GenericDao * Create default L2 network offerings */ void persistDefaultL2NetworkOfferings(); + + NetUtils.InternetProtocol getNetworkOfferingInternetProtocol(long offeringId); + + boolean isIpv6Supported(long offeringId); } diff --git a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java index 93fe2480bbe..fd36a1b825d 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java @@ -42,6 +42,7 @@ import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.net.NetUtils; @Component @DB() @@ -51,6 +52,7 @@ public class NetworkOfferingDaoImpl extends GenericDaoBase AvailabilitySearch; final SearchBuilder AllFieldsSearch; private final GenericSearchBuilder UpgradeSearch; + @Inject NetworkOfferingDetailsDao _detailsDao; @Inject @@ -269,4 +271,16 @@ public class NetworkOfferingDaoImpl extends GenericDaoBase { List listByVmIdAndKeyword(long instanceId, String keyword); NicVO findByInstanceIdAndMacAddress(long instanceId, String macAddress); + + List findNicsByIpv6GatewayIpv6CidrAndReserver(String ipv6Gateway, String ipv6Cidr, String reserverName); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java index 18630e85c57..6c72449e2af 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java @@ -70,6 +70,8 @@ public class NicDaoImpl extends GenericDaoBase implements NicDao { AllFieldsSearch.and("reserverName",AllFieldsSearch.entity().getReserver(),Op.EQ); AllFieldsSearch.and("macAddress", AllFieldsSearch.entity().getMacAddress(), Op.EQ); AllFieldsSearch.and("deviceid", AllFieldsSearch.entity().getDeviceId(), Op.EQ); + AllFieldsSearch.and("ipv6Gateway", AllFieldsSearch.entity().getIPv6Gateway(), Op.EQ); + AllFieldsSearch.and("ipv6Cidr", AllFieldsSearch.entity().getIPv6Cidr(), Op.EQ); AllFieldsSearch.done(); IpSearch = createSearchBuilder(String.class); @@ -372,4 +374,13 @@ public class NicDaoImpl extends GenericDaoBase implements NicDao { sc.setParameters("macAddress", macAddress); return findOneBy(sc); } + + @Override + public List findNicsByIpv6GatewayIpv6CidrAndReserver(String ipv6Gateway, String ipv6Cidr, String reserverName) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("ipv6Gateway", ipv6Gateway); + sc.setParameters("ipv6Cidr", ipv6Cidr); + sc.setParameters("reserverName", reserverName); + return listBy(sc); + } } diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index d7b7d129f27..f13ae6e6803 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -108,6 +108,7 @@ + @@ -199,6 +200,7 @@ + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql index 48f22f906ec..382a2d415a4 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql @@ -700,6 +700,145 @@ CREATE VIEW `cloud`.`domain_router_view` AS and async_job.instance_type = 'DomainRouter' and async_job.job_status = 0; +-- For IPv6 guest prefixes. +CREATE TABLE `cloud`.`dc_ip6_guest_prefix` ( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `uuid` varchar(40) DEFAULT NULL, + `data_center_id` bigint(20) unsigned NOT NULL COMMENT 'zone it belongs to', + `prefix` varchar(255) NOT NULL COMMENT 'prefix of the ipv6 network', + `created` datetime default NULL, + `removed` datetime default NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_dc_ip6_guest_prefix__data_center_id` FOREIGN KEY (`data_center_id`) REFERENCES `data_center`(`id`), + CONSTRAINT `uc_dc_ip6_guest_prefix__uuid` UNIQUE (`uuid`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `cloud`.`ip6_guest_prefix_subnet_network_map` ( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `uuid` varchar(40) DEFAULT NULL, + `prefix_id` bigint(20) unsigned NOT NULL COMMENT 'ip6 guest prefix to which subnet belongs to', + `subnet` varchar(255) NOT NULL COMMENT 'subnet of the ipv6 network', + `network_id` bigint(20) unsigned DEFAULT NULL COMMENT 'network to which subnet is associated to', + `state` varchar(255) NOT NULL COMMENT 'state of the subnet network', + `updated` datetime default NULL, + `created` datetime default NULL, + `removed` datetime default NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_ip6_guest_prefix_subnet_network_map__prefix_id` FOREIGN KEY (`prefix_id`) REFERENCES `dc_ip6_guest_prefix`(`id`), + CONSTRAINT `fk_ip6_guest_prefix_subnet_network_map__network_id` FOREIGN KEY (`network_id`) REFERENCES `networks`(`id`), + CONSTRAINT `uc_ip6_guest_prefix_subnet_network_map__uuid` UNIQUE (`uuid`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Network offering with internet protocol +DROP VIEW IF EXISTS `cloud`.`network_offering_view`; +CREATE VIEW `cloud`.`network_offering_view` AS + SELECT + `network_offerings`.`id` AS `id`, + `network_offerings`.`uuid` AS `uuid`, + `network_offerings`.`name` AS `name`, + `network_offerings`.`unique_name` AS `unique_name`, + `network_offerings`.`display_text` AS `display_text`, + `network_offerings`.`nw_rate` AS `nw_rate`, + `network_offerings`.`mc_rate` AS `mc_rate`, + `network_offerings`.`traffic_type` AS `traffic_type`, + `network_offerings`.`tags` AS `tags`, + `network_offerings`.`system_only` AS `system_only`, + `network_offerings`.`specify_vlan` AS `specify_vlan`, + `network_offerings`.`service_offering_id` AS `service_offering_id`, + `network_offerings`.`conserve_mode` AS `conserve_mode`, + `network_offerings`.`created` AS `created`, + `network_offerings`.`removed` AS `removed`, + `network_offerings`.`default` AS `default`, + `network_offerings`.`availability` AS `availability`, + `network_offerings`.`dedicated_lb_service` AS `dedicated_lb_service`, + `network_offerings`.`shared_source_nat_service` AS `shared_source_nat_service`, + `network_offerings`.`sort_key` AS `sort_key`, + `network_offerings`.`redundant_router_service` AS `redundant_router_service`, + `network_offerings`.`state` AS `state`, + `network_offerings`.`guest_type` AS `guest_type`, + `network_offerings`.`elastic_ip_service` AS `elastic_ip_service`, + `network_offerings`.`eip_associate_public_ip` AS `eip_associate_public_ip`, + `network_offerings`.`elastic_lb_service` AS `elastic_lb_service`, + `network_offerings`.`specify_ip_ranges` AS `specify_ip_ranges`, + `network_offerings`.`inline` AS `inline`, + `network_offerings`.`is_persistent` AS `is_persistent`, + `network_offerings`.`internal_lb` AS `internal_lb`, + `network_offerings`.`public_lb` AS `public_lb`, + `network_offerings`.`egress_default_policy` AS `egress_default_policy`, + `network_offerings`.`concurrent_connections` AS `concurrent_connections`, + `network_offerings`.`keep_alive_enabled` AS `keep_alive_enabled`, + `network_offerings`.`supports_streched_l2` AS `supports_streched_l2`, + `network_offerings`.`supports_public_access` AS `supports_public_access`, + `network_offerings`.`for_vpc` AS `for_vpc`, + `network_offerings`.`service_package_id` AS `service_package_id`, + GROUP_CONCAT(DISTINCT(domain.id)) AS domain_id, + GROUP_CONCAT(DISTINCT(domain.uuid)) AS domain_uuid, + GROUP_CONCAT(DISTINCT(domain.name)) AS domain_name, + GROUP_CONCAT(DISTINCT(domain.path)) AS domain_path, + GROUP_CONCAT(DISTINCT(zone.id)) AS zone_id, + GROUP_CONCAT(DISTINCT(zone.uuid)) AS zone_uuid, + GROUP_CONCAT(DISTINCT(zone.name)) AS zone_name, + `offering_details`.value AS internet_protocol + FROM + `cloud`.`network_offerings` + LEFT JOIN + `cloud`.`network_offering_details` AS `domain_details` ON `domain_details`.`network_offering_id` = `network_offerings`.`id` AND `domain_details`.`name`='domainid' + LEFT JOIN + `cloud`.`domain` AS `domain` ON FIND_IN_SET(`domain`.`id`, `domain_details`.`value`) + LEFT JOIN + `cloud`.`network_offering_details` AS `zone_details` ON `zone_details`.`network_offering_id` = `network_offerings`.`id` AND `zone_details`.`name`='zoneid' + LEFT JOIN + `cloud`.`data_center` AS `zone` ON FIND_IN_SET(`zone`.`id`, `zone_details`.`value`) + LEFT JOIN + `cloud`.`network_offering_details` AS `offering_details` ON `offering_details`.`network_offering_id` = `network_offerings`.`id` AND `offering_details`.`name`='internetProtocol' + GROUP BY + `network_offerings`.`id`; + +-- VPC offering with multi-domains and multi-zones +DROP VIEW IF EXISTS `cloud`.`vpc_offering_view`; +CREATE VIEW `cloud`.`vpc_offering_view` AS + SELECT + `vpc_offerings`.`id` AS `id`, + `vpc_offerings`.`uuid` AS `uuid`, + `vpc_offerings`.`name` AS `name`, + `vpc_offerings`.`unique_name` AS `unique_name`, + `vpc_offerings`.`display_text` AS `display_text`, + `vpc_offerings`.`state` AS `state`, + `vpc_offerings`.`default` AS `default`, + `vpc_offerings`.`created` AS `created`, + `vpc_offerings`.`removed` AS `removed`, + `vpc_offerings`.`service_offering_id` AS `service_offering_id`, + `vpc_offerings`.`supports_distributed_router` AS `supports_distributed_router`, + `vpc_offerings`.`supports_region_level_vpc` AS `supports_region_level_vpc`, + `vpc_offerings`.`redundant_router_service` AS `redundant_router_service`, + `vpc_offerings`.`sort_key` AS `sort_key`, + GROUP_CONCAT(DISTINCT(domain.id)) AS domain_id, + GROUP_CONCAT(DISTINCT(domain.uuid)) AS domain_uuid, + GROUP_CONCAT(DISTINCT(domain.name)) AS domain_name, + GROUP_CONCAT(DISTINCT(domain.path)) AS domain_path, + GROUP_CONCAT(DISTINCT(zone.id)) AS zone_id, + GROUP_CONCAT(DISTINCT(zone.uuid)) AS zone_uuid, + GROUP_CONCAT(DISTINCT(zone.name)) AS zone_name, + `offering_details`.value AS internet_protocol + FROM + `cloud`.`vpc_offerings` + LEFT JOIN + `cloud`.`vpc_offering_details` AS `domain_details` ON `domain_details`.`offering_id` = `vpc_offerings`.`id` AND `domain_details`.`name`='domainid' + LEFT JOIN + `cloud`.`domain` AS `domain` ON FIND_IN_SET(`domain`.`id`, `domain_details`.`value`) + LEFT JOIN + `cloud`.`vpc_offering_details` AS `zone_details` ON `zone_details`.`offering_id` = `vpc_offerings`.`id` AND `zone_details`.`name`='zoneid' + LEFT JOIN + `cloud`.`data_center` AS `zone` ON FIND_IN_SET(`zone`.`id`, `zone_details`.`value`) + LEFT JOIN + `cloud`.`vpc_offering_details` AS `offering_details` ON `offering_details`.`offering_id` = `vpc_offerings`.`id` AND `offering_details`.`name`='internetprotocol' + GROUP BY + `vpc_offerings`.`id`; + +-- Allow storing IPv6 CIDRs +ALTER TABLE `cloud`.`firewall_rules_cidrs` MODIFY COLUMN `source_cidr` varchar(43) DEFAULT NULL; +ALTER TABLE `cloud`.`firewall_rules_dcidrs` MODIFY COLUMN `destination_cidr` varchar(43) DEFAULT NULL; + -- -- Management Server Status -- diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockNetworkManager.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockNetworkManager.java index c44a1235627..e5e98cd4296 100644 --- a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockNetworkManager.java +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockNetworkManager.java @@ -36,6 +36,7 @@ import com.cloud.agent.api.routing.IpAssocCommand; import com.cloud.agent.api.routing.IpAssocVpcCommand; import com.cloud.agent.api.routing.LoadBalancerConfigCommand; import com.cloud.agent.api.routing.SetFirewallRulesCommand; +import com.cloud.agent.api.routing.SetIpv6FirewallRulesCommand; import com.cloud.agent.api.routing.SetNetworkACLAnswer; import com.cloud.agent.api.routing.SetNetworkACLCommand; import com.cloud.agent.api.routing.SetPortForwardingRulesAnswer; @@ -58,6 +59,8 @@ public interface MockNetworkManager extends Manager { Answer SetFirewallRules(SetFirewallRulesCommand cmd); + Answer SetIpv6FirewallRules(SetIpv6FirewallRulesCommand cmd); + Answer getNetworkUsage(NetworkUsageCommand cmd); Answer IpAssoc(IpAssocCommand cmd); diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockNetworkManagerImpl.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockNetworkManagerImpl.java index 41f9ce24585..a71d71f07cb 100644 --- a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockNetworkManagerImpl.java +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/MockNetworkManagerImpl.java @@ -43,6 +43,8 @@ import com.cloud.agent.api.routing.LoadBalancerConfigCommand; import com.cloud.agent.api.routing.NetworkElementCommand; import com.cloud.agent.api.routing.SetFirewallRulesAnswer; import com.cloud.agent.api.routing.SetFirewallRulesCommand; +import com.cloud.agent.api.routing.SetIpv6FirewallRulesAnswer; +import com.cloud.agent.api.routing.SetIpv6FirewallRulesCommand; import com.cloud.agent.api.routing.SetNetworkACLAnswer; import com.cloud.agent.api.routing.SetNetworkACLCommand; import com.cloud.agent.api.routing.SetPortForwardingRulesAnswer; @@ -96,6 +98,16 @@ public class MockNetworkManagerImpl extends ManagerBase implements MockNetworkMa return new SetFirewallRulesAnswer(cmd, true, results); } + @Override + public SetIpv6FirewallRulesAnswer SetIpv6FirewallRules(SetIpv6FirewallRulesCommand cmd) { + String[] results = new String[cmd.getRules().length]; + String routerIp = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP); + if (routerIp == null) { + return new SetIpv6FirewallRulesAnswer(cmd, false, results); + } + return new SetIpv6FirewallRulesAnswer(cmd, true, results); + } + @Override public NetworkUsageAnswer getNetworkUsage(NetworkUsageCommand cmd) { return new NetworkUsageAnswer(cmd, null, 100L, 100L); diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java index 29ad3cc4969..159f22236c2 100644 --- a/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/agent/manager/SimulatorManagerImpl.java @@ -26,20 +26,16 @@ import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; -import org.apache.cloudstack.diagnostics.DiagnosticsCommand; -import org.apache.log4j.Logger; -import org.springframework.stereotype.Component; - -import com.google.gson.Gson; -import com.google.gson.stream.JsonReader; - import org.apache.cloudstack.ca.SetupCertificateCommand; import org.apache.cloudstack.ca.SetupKeyStoreCommand; +import org.apache.cloudstack.diagnostics.DiagnosticsCommand; import org.apache.cloudstack.storage.command.DeleteCommand; import org.apache.cloudstack.storage.command.DownloadCommand; import org.apache.cloudstack.storage.command.DownloadProgressCommand; import org.apache.cloudstack.storage.command.StorageSubSystemCommand; import org.apache.cloudstack.storage.command.UploadStatusCommand; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; import com.cloud.agent.api.Answer; import com.cloud.agent.api.AttachIsoCommand; @@ -102,6 +98,7 @@ import com.cloud.agent.api.routing.LoadBalancerConfigCommand; import com.cloud.agent.api.routing.RemoteAccessVpnCfgCommand; import com.cloud.agent.api.routing.SavePasswordCommand; import com.cloud.agent.api.routing.SetFirewallRulesCommand; +import com.cloud.agent.api.routing.SetIpv6FirewallRulesCommand; import com.cloud.agent.api.routing.SetMonitorServiceCommand; import com.cloud.agent.api.routing.SetNetworkACLCommand; import com.cloud.agent.api.routing.SetPortForwardingRulesCommand; @@ -139,6 +136,8 @@ import com.cloud.utils.db.DB; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine.PowerState; +import com.google.gson.Gson; +import com.google.gson.stream.JsonReader; @Component public class SimulatorManagerImpl extends ManagerBase implements SimulatorManager, PluggableService { @@ -313,6 +312,8 @@ public class SimulatorManagerImpl extends ManagerBase implements SimulatorManage answer = _mockNetworkMgr.SetStaticNatRules((SetStaticNatRulesCommand)cmd); } else if (cmd instanceof SetFirewallRulesCommand) { answer = _mockNetworkMgr.SetFirewallRules((SetFirewallRulesCommand)cmd); + } else if (cmd instanceof SetIpv6FirewallRulesCommand) { + answer = _mockNetworkMgr.SetIpv6FirewallRules((SetIpv6FirewallRulesCommand)cmd); } else if (cmd instanceof SetPortForwardingRulesCommand) { answer = _mockNetworkMgr.SetPortForwardingRules((SetPortForwardingRulesCommand)cmd); } else if (cmd instanceof NetworkUsageCommand) { diff --git a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java index 1b8ab6ab39e..1a92945131d 100644 --- a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java +++ b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java @@ -34,15 +34,6 @@ import java.util.TimerTask; import javax.inject.Inject; import javax.naming.ConfigurationException; -import net.juniper.contrail.api.ApiConnector; -import net.juniper.contrail.api.ApiConnectorFactory; -import net.juniper.contrail.api.ApiPropertyBase; -import net.juniper.contrail.api.ObjectReference; -import net.juniper.contrail.api.types.FloatingIp; -import net.juniper.contrail.api.types.FloatingIpPool; -import net.juniper.contrail.api.types.NetworkPolicy; -import net.juniper.contrail.api.types.VirtualNetwork; - import org.apache.cloudstack.network.contrail.model.FloatingIpModel; import org.apache.cloudstack.network.contrail.model.FloatingIpPoolModel; import org.apache.cloudstack.network.contrail.model.ModelController; @@ -99,6 +90,15 @@ import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; import com.google.common.collect.ImmutableList; +import net.juniper.contrail.api.ApiConnector; +import net.juniper.contrail.api.ApiConnectorFactory; +import net.juniper.contrail.api.ApiPropertyBase; +import net.juniper.contrail.api.ObjectReference; +import net.juniper.contrail.api.types.FloatingIp; +import net.juniper.contrail.api.types.FloatingIpPool; +import net.juniper.contrail.api.types.NetworkPolicy; +import net.juniper.contrail.api.types.VirtualNetwork; + public class ContrailManagerImpl extends ManagerBase implements ContrailManager { @Inject public ConfigurationService _configService; @@ -219,7 +219,7 @@ public class ContrailManagerImpl extends ManagerBase implements ContrailManager ConfigurationManager configMgr = (ConfigurationManager) _configService; NetworkOfferingVO voffer = configMgr.createNetworkOffering(offeringName, offeringDisplayText, TrafficType.Public, null, true, Availability.Optional, null, serviceProviderMap, true, - Network.GuestType.Shared, false, null, false, null, true, false, null, true, null, false, false, null, null, true); + Network.GuestType.Shared, false, null, false, null, true, false, null, true, null, false, false, null, null, true, null); long id = voffer.getId(); _networkOfferingDao.update(id, voffer); return _networkOfferingDao.findById(id); @@ -254,7 +254,7 @@ public class ContrailManagerImpl extends ManagerBase implements ContrailManager ConfigurationManager configMgr = (ConfigurationManager)_configService; NetworkOfferingVO voffer = configMgr.createNetworkOffering(offeringName, offeringDisplayText, TrafficType.Guest, null, false, Availability.Optional, null, serviceProviderMap, true, - Network.GuestType.Isolated, false, null, false, null, false, true, null, true, null, false, offeringName.equals(vpcRouterOfferingName), null, null, true); + Network.GuestType.Isolated, false, null, false, null, false, true, null, true, null, false, offeringName.equals(vpcRouterOfferingName), null, null, true, null); if (offeringName.equals(vpcRouterOfferingName)) { voffer.setInternalLb(true); } @@ -295,7 +295,7 @@ public class ContrailManagerImpl extends ManagerBase implements ContrailManager } serviceProviderMap.put(svc, providerSet); } - vpcOffer = _vpcProvSvc.createVpcOffering(juniperVPCOfferingName, juniperVPCOfferingDisplayText, services, serviceProviderMap, null, null, null, null, VpcOffering.State.Enabled); + vpcOffer = _vpcProvSvc.createVpcOffering(juniperVPCOfferingName, juniperVPCOfferingDisplayText, services, serviceProviderMap, null, null, null, null, null, VpcOffering.State.Enabled); long id = vpcOffer.getId(); _vpcOffDao.update(id, (VpcOfferingVO)vpcOffer); return _vpcOffDao.findById(id); diff --git a/scripts/vm/network/vnet/modifyvlan.sh b/scripts/vm/network/vnet/modifyvlan.sh index 25008b15429..30efc907111 100755 --- a/scripts/vm/network/vnet/modifyvlan.sh +++ b/scripts/vm/network/vnet/modifyvlan.sh @@ -34,6 +34,7 @@ addVlan() { if [ ! -d /sys/class/net/$vlanDev ] then ip link add link $pif name $vlanDev type vlan id $vlanId > /dev/null + echo 1 > /proc/sys/net/ipv6/conf/$vlanDev/disable_ipv6 ip link set $vlanDev up if [ $? -gt 0 ] @@ -47,12 +48,15 @@ addVlan() { fi fi + # disable IPv6 + echo 1 > /proc/sys/net/ipv6/conf/$vlanDev/disable_ipv6 # is up? ip link set $vlanDev up > /dev/null 2>/dev/null if [ ! -d /sys/class/net/$vlanBr ] then ip link add name $vlanBr type bridge + echo 1 > /proc/sys/net/ipv6/conf/$vlanBr/disable_ipv6 ip link set $vlanBr up if [ $? -gt 0 ] @@ -80,6 +84,8 @@ addVlan() { fi fi fi + # disable IPv6 + echo 1 > /proc/sys/net/ipv6/conf/$vlanBr/disable_ipv6 # is vlanBr up? ip link set $vlanBr up > /dev/null 2>/dev/null diff --git a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java index 51e5051bc9b..ca26de5cf21 100644 --- a/server/src/main/java/com/cloud/alert/AlertManagerImpl.java +++ b/server/src/main/java/com/cloud/alert/AlertManagerImpl.java @@ -21,8 +21,10 @@ import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Timer; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -38,6 +40,11 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.managed.context.ManagedContextTimerTask; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.utils.mailing.MailAddress; +import org.apache.cloudstack.utils.mailing.SMTPMailProperties; +import org.apache.cloudstack.utils.mailing.SMTPMailSender; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.math.NumberUtils; import org.apache.log4j.Logger; import com.cloud.alert.dao.AlertDao; @@ -64,22 +71,17 @@ import com.cloud.event.AlertGenerator; import com.cloud.event.EventTypes; import com.cloud.host.Host; import com.cloud.host.HostVO; +import com.cloud.network.Ipv6Service; import com.cloud.network.dao.IPAddressDao; import com.cloud.org.Grouping.AllocationState; import com.cloud.resource.ResourceManager; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.StorageManager; +import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.SearchCriteria; -import java.util.HashSet; -import java.util.Set; -import org.apache.cloudstack.utils.mailing.MailAddress; -import org.apache.cloudstack.utils.mailing.SMTPMailProperties; -import org.apache.cloudstack.utils.mailing.SMTPMailSender; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.math.NumberUtils; public class AlertManagerImpl extends ManagerBase implements AlertManager, Configurable { protected Logger logger = Logger.getLogger(AlertManagerImpl.class.getName()); @@ -119,6 +121,8 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi protected ConfigDepot _configDepot; @Inject ServiceOfferingDao _offeringsDao; + @Inject + Ipv6Service ipv6Service; private Timer _timer = null; private long _capacityCheckPeriod = 60L * 60L * 1000L; // One hour by default. @@ -196,6 +200,7 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi _capacityTypeThresholdMap.put(Capacity.CAPACITY_TYPE_VLAN, _vlanCapacityThreshold); _capacityTypeThresholdMap.put(Capacity.CAPACITY_TYPE_DIRECT_ATTACHED_PUBLIC_IP, _directNetworkPublicIpCapacityThreshold); _capacityTypeThresholdMap.put(Capacity.CAPACITY_TYPE_LOCAL_STORAGE, _localStorageCapacityThreshold); + _capacityTypeThresholdMap.put(Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET, Ipv6SubnetCapacityThreshold.value()); String capacityCheckPeriodStr = configs.get("capacity.check.period"); if (capacityCheckPeriodStr != null) { @@ -314,6 +319,7 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi // Calculate new Public IP capacity for Virtual Network if (datacenter.getNetworkType() == NetworkType.Advanced) { createOrUpdateIpCapacity(dcId, null, Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_PUBLIC_IP, datacenter.getAllocationState()); + createOrUpdateIpv6Capacity(dcId, datacenter.getAllocationState()); } // Calculate new Public IP capacity for Direct Attached Network @@ -416,6 +422,31 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi } } + public void createOrUpdateIpv6Capacity(Long dcId, AllocationState capacityState) { + final short capacityType = Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET; + SearchCriteria capacitySC = _capacityDao.createSearchCriteria(); + capacitySC.addAnd("dataCenterId", SearchCriteria.Op.EQ, dcId); + capacitySC.addAnd("capacityType", SearchCriteria.Op.EQ, capacityType); + + List capacities = _capacityDao.search(capacitySC, null); + Pair usedTotal = ipv6Service.getUsedTotalIpv6SubnetForZone(dcId); + int total = usedTotal.second(); + int allocated = usedTotal.first(); + CapacityState state = (capacityState == AllocationState.Disabled) ? CapacityState.Disabled : CapacityState.Enabled; + if (capacities.size() == 0) { + CapacityVO capacityVO = new CapacityVO(null, dcId, null, null, allocated, total, capacityType); + capacityVO.setCapacityState(state); + _capacityDao.persist(capacityVO); + } else if (!(capacities.get(0).getUsedCapacity() == allocated && capacities.get(0).getTotalCapacity() == total + && capacities.get(0).getCapacityState() == state)) { + CapacityVO capacity = capacities.get(0); + capacity.setUsedCapacity(allocated); + capacity.setTotalCapacity(total); + capacity.setCapacityState(state); + _capacityDao.update(capacity.getId(), capacity); + } + } + class CapacityChecker extends ManagedContextTimerTask { @Override protected void runInContext() { @@ -626,6 +657,13 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi msgContent = "Number of unallocated VLANs is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)"; alertType = AlertManager.AlertType.ALERT_TYPE_VLAN; break; + case Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET: + msgSubject = "System Alert: Number of unallocated virtual network guest IPv6 subnets is low in availability zone " + dc.getName(); + totalStr = Double.toString(totalCapacity); + usedStr = Double.toString(usedCapacity); + msgContent = "Number of unallocated virtual network guest IPv6 subnets is low, total: " + totalStr + ", allocated: " + usedStr + " (" + pctStr + "%)"; + alertType = AlertManager.AlertType.ALERT_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET; + break; } try { @@ -646,6 +684,7 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi dataCenterCapacityTypes.add(Capacity.CAPACITY_TYPE_DIRECT_ATTACHED_PUBLIC_IP); dataCenterCapacityTypes.add(Capacity.CAPACITY_TYPE_SECONDARY_STORAGE); dataCenterCapacityTypes.add(Capacity.CAPACITY_TYPE_VLAN); + dataCenterCapacityTypes.add(Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET); return dataCenterCapacityTypes; } @@ -760,7 +799,7 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[] {CPUCapacityThreshold, MemoryCapacityThreshold, StorageAllocatedCapacityThreshold, StorageCapacityThreshold, AlertSmtpEnabledSecurityProtocols, - AlertSmtpUseStartTLS}; + AlertSmtpUseStartTLS, Ipv6SubnetCapacityThreshold}; } @Override diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 23e329e5a29..d7f03bc51c2 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -28,6 +28,7 @@ import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -36,7 +37,6 @@ import java.util.stream.Collectors; import javax.inject.Inject; -import com.cloud.utils.security.CertificateHelper; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroup; @@ -71,6 +71,8 @@ import org.apache.cloudstack.api.response.ControlledViewEntityResponse; import org.apache.cloudstack.api.response.CounterResponse; import org.apache.cloudstack.api.response.CreateCmdResponse; import org.apache.cloudstack.api.response.CreateSSHKeyPairResponse; +import org.apache.cloudstack.api.response.DataCenterGuestIpv6PrefixResponse; +import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse; import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; @@ -93,6 +95,7 @@ import org.apache.cloudstack.api.response.InstanceGroupResponse; import org.apache.cloudstack.api.response.InternalLoadBalancerElementResponse; import org.apache.cloudstack.api.response.IpForwardingRuleResponse; import org.apache.cloudstack.api.response.IpRangeResponse; +import org.apache.cloudstack.api.response.Ipv6RouteResponse; import org.apache.cloudstack.api.response.IsolationMethodResponse; import org.apache.cloudstack.api.response.LBHealthCheckPolicyResponse; import org.apache.cloudstack.api.response.LBHealthCheckResponse; @@ -125,7 +128,6 @@ import org.apache.cloudstack.api.response.ResourceCountResponse; import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.ResourceLimitResponse; import org.apache.cloudstack.api.response.ResourceTagResponse; -import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse; import org.apache.cloudstack.api.response.RollingMaintenanceHostSkippedResponse; import org.apache.cloudstack.api.response.RollingMaintenanceHostUpdatedResponse; import org.apache.cloudstack.api.response.RollingMaintenanceResponse; @@ -168,6 +170,7 @@ import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.direct.download.DirectDownloadCertificate; import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap; import org.apache.cloudstack.direct.download.DirectDownloadManager; import org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus.CertificateStatus; @@ -176,7 +179,6 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; -import org.apache.cloudstack.direct.download.DirectDownloadCertificate; import org.apache.cloudstack.framework.jobs.AsyncJob; import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.management.ManagementServerHost; @@ -192,6 +194,7 @@ import org.apache.cloudstack.usage.Usage; import org.apache.cloudstack.usage.UsageService; import org.apache.cloudstack.usage.UsageTypes; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.cloud.agent.api.VgpuTypesInfo; @@ -231,6 +234,7 @@ import com.cloud.configuration.ResourceLimit; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterGuestIpv6Prefix; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; import com.cloud.dc.Pod; @@ -250,6 +254,7 @@ import com.cloud.hypervisor.HypervisorCapabilities; import com.cloud.network.GuestVlan; import com.cloud.network.GuestVlanRange; import com.cloud.network.IpAddress; +import com.cloud.network.Ipv6Service; import com.cloud.network.Network; import com.cloud.network.Network.Capability; import com.cloud.network.Network.Provider; @@ -312,6 +317,7 @@ import com.cloud.offering.NetworkOffering; import com.cloud.offering.NetworkOffering.Detail; import com.cloud.offering.ServiceOffering; import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.org.Cluster; import com.cloud.projects.Project; import com.cloud.projects.ProjectAccount; @@ -362,6 +368,7 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.Dhcp; import com.cloud.utils.net.Ip; import com.cloud.utils.net.NetUtils; +import com.cloud.utils.security.CertificateHelper; import com.cloud.vm.ConsoleProxyVO; import com.cloud.vm.InstanceGroup; import com.cloud.vm.Nic; @@ -377,7 +384,7 @@ import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.snapshot.VMSnapshot; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; -import org.apache.commons.lang3.StringUtils; + import sun.security.x509.X509CertImpl; public class ApiResponseHelper implements ResponseGenerator { @@ -431,6 +438,10 @@ public class ApiResponseHelper implements ResponseGenerator { private UserStatisticsDao userStatsDao; @Inject private NetworkDao networkDao; + @Inject + NetworkOfferingDao networkOfferingDao; + @Inject + Ipv6Service ipv6Service; @Override public UserResponse createUserResponse(User user) { @@ -791,8 +802,13 @@ public class ApiResponseHelper implements ResponseGenerator { } } - vlanResponse.setGateway(vlan.getVlanGateway()); - vlanResponse.setNetmask(vlan.getVlanNetmask()); + String gateway = vlan.getVlanGateway(); + String netmask = vlan.getVlanNetmask(); + vlanResponse.setGateway(gateway); + vlanResponse.setNetmask(netmask); + if (StringUtils.isNotEmpty(gateway) && StringUtils.isNotEmpty(netmask)) { + vlanResponse.setCidr(NetUtils.getCidrFromGatewayAndNetmask(gateway, netmask)); + } // get start ip and end ip of corresponding vlan String ipRange = vlan.getIpRange(); @@ -864,7 +880,7 @@ public class ApiResponseHelper implements ResponseGenerator { SearchCriteria sc = sb.create(); sc.setParameters("vlanId", vlanId); IPAddressVO userIpAddresVO = userIpAddressDao.findOneBy(sc); - return userIpAddresVO.isForSystemVms(); + return userIpAddresVO != null ? userIpAddresVO.isForSystemVms() : false; } @Override @@ -1182,6 +1198,7 @@ public class ApiResponseHelper implements ResponseGenerator { capacityResponses.addAll(getStatsCapacityresponse(null, null, pod.getId(), pod.getDataCenterId())); podResponse.setCapacities(new ArrayList(capacityResponses)); } + podResponse.setHasAnnotation(annotationDao.hasAnnotations(pod.getUuid(), AnnotationService.EntityType.POD.name(), _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); podResponse.setObjectName("pod"); @@ -1248,6 +1265,23 @@ public class ApiResponseHelper implements ResponseGenerator { return capacityResponses; } + @Override + public DataCenterGuestIpv6PrefixResponse createDataCenterGuestIpv6PrefixResponse(DataCenterGuestIpv6Prefix prefix) { + DataCenterGuestIpv6PrefixResponse response = new DataCenterGuestIpv6PrefixResponse(); + response.setId(prefix.getUuid()); + response.setPrefix(prefix.getPrefix()); + DataCenter dc = ApiDBUtils.findZoneById(prefix.getDataCenterId()); + response.setZoneId(dc.getUuid()); + Pair usedTotal = ipv6Service.getUsedTotalIpv6SubnetForPrefix(prefix); + int used = usedTotal.first(); + int total = usedTotal.second(); + response.setUsedSubnets(used); + response.setAvailableSubnets(total - used); + response.setTotalSubnets(total); + response.setCreated(prefix.getCreated()); + return response; + } + @Override public VolumeResponse createVolumeResponse(ResponseView view, Volume volume) { List viewVrs = ApiDBUtils.newVolumeView(volume); @@ -2498,6 +2532,19 @@ public class ApiResponseHelper implements ResponseGenerator { response.setBytesReceived(bytesReceived); response.setBytesSent(bytesSent); + if (networkOfferingDao.isIpv6Supported(network.getNetworkOfferingId())) { + response.setInternetProtocol(networkOfferingDao.getNetworkOfferingInternetProtocol(network.getNetworkOfferingId()).toString()); + response.setIpv6Routing(Network.Routing.Static.toString()); + response.setIpv6Routes(new LinkedHashSet<>()); + if (Network.GuestType.Isolated.equals(networkOffering.getGuestType())) { + List ipv6Addresses = ipv6Service.getPublicIpv6AddressesForNetwork(network); + for (String address : ipv6Addresses) { + Ipv6RouteResponse route = new Ipv6RouteResponse(network.getIp6Cidr(), address); + response.addIpv6Route(route); + } + } + } + response.setObjectName("network"); return response; } @@ -3186,6 +3233,7 @@ public class ApiResponseHelper implements ResponseGenerator { response.setTags(tagResponses); response.setHasAnnotation(annotationDao.hasAnnotations(vpc.getUuid(), AnnotationService.EntityType.VPC.name(), _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); + ipv6Service.updateIpv6RoutesForVpcResponse(vpc, response); response.setObjectName("vpc"); return response; } @@ -4749,4 +4797,46 @@ public class ApiResponseHelper implements ResponseGenerator { CertificateStatus status = result != null && result.first() ? CertificateStatus.UPLOADED : CertificateStatus.FAILED; return getDirectDownloadHostStatusResponseInternal(host, status, result != null ? result.second() : "provision certificate failure"); } + + @Override + public FirewallResponse createIpv6FirewallRuleResponse(FirewallRule fwRule) { + FirewallResponse response = new FirewallResponse(); + + response.setId(fwRule.getUuid()); + response.setProtocol(fwRule.getProtocol()); + List cidrs = ApiDBUtils.findFirewallSourceCidrs(fwRule.getId()); + response.setCidrList(StringUtils.join(cidrs, ",")); + List destinationCidrs = ApiDBUtils.findFirewallDestCidrs(fwRule.getId()); + response.setDestCidr(StringUtils.join(destinationCidrs, ",")); + response.setTrafficType(fwRule.getTrafficType().toString()); + response.setProtocol(fwRule.getProtocol()); + response.setStartPort(fwRule.getSourcePortStart()); + response.setEndPort(fwRule.getSourcePortEnd()); + response.setIcmpCode(fwRule.getIcmpCode()); + response.setIcmpType(fwRule.getIcmpType()); + + Network network = ApiDBUtils.findNetworkById(fwRule.getNetworkId()); + response.setNetworkId(network.getUuid()); + + FirewallRule.State state = fwRule.getState(); + String stateToSet = state.toString(); + if (state.equals(FirewallRule.State.Revoke)) { + stateToSet = "Deleting"; + } + + response.setForDisplay(fwRule.isDisplay()); + + // set tag information + List tags = ApiDBUtils.listByResourceTypeAndId(ResourceObjectType.FirewallRule, fwRule.getId()); + List tagResponses = new ArrayList(); + for (ResourceTag tag : tags) { + ResourceTagResponse tagResponse = createResourceTagResponse(tag, true); + CollectionUtils.addIgnoreNull(tagResponses, tagResponse); + } + response.setTags(tagResponses); + + response.setState(stateToSet); + response.setObjectName("firewallrule"); + return response; + } } diff --git a/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java index ee3e0f21829..ae51df3cb65 100644 --- a/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java @@ -94,10 +94,12 @@ public class NetworkOfferingJoinDaoImpl extends GenericDaoBase Ipv6Service.IPV6_SLAAC_CIDR_NETMASK) { + throw new InvalidParameterValueException(String.format("IPv6 prefix must be /%d or less", Ipv6Service.IPV6_SLAAC_CIDR_NETMASK)); + } + List existingPrefixes = dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId); + for (DataCenterGuestIpv6PrefixVO existingPrefix : existingPrefixes) { + IPv6Network existingPrefixNet = IPv6Network.fromString(existingPrefix.getPrefix()); + if (NetUtils.ipv6NetworksOverlap(existingPrefixNet, prefixNet)) { + throw new InvalidParameterValueException(String.format("IPv6 prefix %s overlaps with the existing IPv6 prefix %s", prefixNet, existingPrefixNet)); + } + } + DataCenterGuestIpv6Prefix dataCenterGuestIpv6Prefix = null; + try { + dataCenterGuestIpv6Prefix = Transaction.execute(new TransactionCallback() { + @Override + public DataCenterGuestIpv6Prefix doInTransaction(TransactionStatus status) { + DataCenterGuestIpv6PrefixVO dataCenterGuestIpv6PrefixVO = new DataCenterGuestIpv6PrefixVO(zoneId, prefix); + dataCenterGuestIpv6PrefixDao.persist(dataCenterGuestIpv6PrefixVO); + return dataCenterGuestIpv6PrefixVO; + } + }); + } catch (final Exception e) { + s_logger.error(String.format("Unable to add IPv6 prefix for zone: %s due to %s", zone, e.getMessage()), e); + throw new CloudRuntimeException(String.format("Unable to add IPv6 prefix for zone ID: %s. Please contact Cloud Support.", zone.getUuid())); + } + return dataCenterGuestIpv6Prefix; + } + + @Override + public List listDataCenterGuestIpv6Prefixes(final ListGuestNetworkIpv6PrefixesCmd cmd) throws ConcurrentOperationException { + final Long id = cmd.getId(); + final Long zoneId = cmd.getZoneId(); + if (id != null) { + DataCenterGuestIpv6PrefixVO prefix = dataCenterGuestIpv6PrefixDao.findById(id); + List prefixes = new ArrayList<>(); + if (prefix != null) { + prefixes.add(prefix); + } + return prefixes; + } + if (zoneId != null) { + final DataCenterVO zone = _zoneDao.findById(zoneId); + if (zone == null) { + throw new InvalidParameterValueException("Unable to find zone by id: " + zoneId); + } + return dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId); + } + return dataCenterGuestIpv6PrefixDao.listAll(); + } + + @Override + public boolean deleteDataCenterGuestIpv6Prefix(DeleteGuestNetworkIpv6PrefixCmd cmd) { + final long prefixId = cmd.getId(); + final DataCenterGuestIpv6PrefixVO prefix = dataCenterGuestIpv6PrefixDao.findById(prefixId); + if (prefix == null) { + throw new InvalidParameterValueException("Unable to find guest network IPv6 prefix by id: " + prefixId); + } + List prefixSubnets = ipv6GuestPrefixSubnetNetworkMapDao.listUsedByPrefix(prefixId); + if (CollectionUtils.isNotEmpty(prefixSubnets)) { + List usedSubnets = prefixSubnets.stream().map(Ipv6GuestPrefixSubnetNetworkMapVO::getSubnet).collect(Collectors.toList()); + s_logger.error(String.format("Subnets for guest IPv6 prefix {ID: %s, %s} are in use: %s", prefix.getUuid(), prefix.getPrefix(), String.join(", ", usedSubnets))); + throw new CloudRuntimeException(String.format("Unable to delete guest network IPv6 prefix ID: %s. Prefix subnets are in use.", prefix.getUuid())); + } + ipv6GuestPrefixSubnetNetworkMapDao.deleteByPrefixId(prefixId); + dataCenterGuestIpv6PrefixDao.remove(prefixId); + return true; + } + @Override @ActionEvent(eventType = EventTypes.EVENT_POD_EDIT, eventDescription = "updating pod", async = false) public Pod editPod(final UpdatePodCmd cmd) { @@ -4043,6 +4137,11 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati if (endIPv6 == null && startIPv6 != null) { endIPv6 = startIPv6; } + + IPv6Network iPv6Network = IPv6Network.fromString(ip6Cidr); + if (iPv6Network.getNetmask().asPrefixLength() > Ipv6Service.IPV6_SLAAC_CIDR_NETMASK) { + throw new InvalidParameterValueException(String.format("For IPv6 range, prefix must be /%d or less", Ipv6Service.IPV6_SLAAC_CIDR_NETMASK)); + } } if (projectId != null) { @@ -4086,8 +4185,6 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati zoneId = network.getDataCenterId(); physicalNetworkId = network.getPhysicalNetworkId(); } - } else if (ipv6) { - throw new InvalidParameterValueException("Only support IPv6 on extending existed network"); } // Verify that zone exists @@ -4096,11 +4193,6 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException("Unable to find zone by id " + zoneId); } - if (ipv6) { - if (network.getGuestType() != GuestType.Shared || zone.isSecurityGroupEnabled()) { - throw new InvalidParameterValueException("Only support IPv6 on extending existed share network without SG"); - } - } // verify that physical network exists PhysicalNetworkVO pNtwk = null; if (physicalNetworkId != null) { @@ -4535,7 +4627,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati String ipv6Range = null; if (ipv6) { ipv6Range = startIPv6; - if (endIPv6 != null) { + if (StringUtils.isNotEmpty(ipv6Range) && StringUtils.isNotEmpty(endIPv6)) { ipv6Range += "-" + endIPv6; } @@ -4544,17 +4636,23 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati if (vlan.getIp6Gateway() == null) { continue; } - if (NetUtils.isSameIsolationId(vlanId, vlan.getVlanTag())) { - if (NetUtils.isIp6RangeOverlap(ipv6Range, vlan.getIp6Range())) { - throw new InvalidParameterValueException("The IPv6 range with tag: " + vlan.getVlanTag() - + " already has IPs that overlap with the new range. Please specify a different start IP/end IP."); - } - - if (!vlanIp6Gateway.equals(vlan.getIp6Gateway())) { - throw new InvalidParameterValueException("The IP range with tag: " + vlan.getVlanTag() + " has already been added with gateway " + vlan.getIp6Gateway() - + ". Please specify a different tag."); + if ((StringUtils.isAllEmpty(ipv6Range, vlan.getIp6Range())) && + NetUtils.ipv6NetworksOverlap(IPv6Network.fromString(vlanIp6Cidr), IPv6Network.fromString(vlan.getIp6Cidr()))) { + throw new InvalidParameterValueException(String.format("The IPv6 range with tag: %s already has IPs that overlap with the new range.", + vlan.getVlanTag())); + } + if (!StringUtils.isAllEmpty(ipv6Range, vlan.getIp6Range())) { + String r1 = StringUtils.isEmpty(ipv6Range) ? NetUtils.getIpv6RangeFromCidr(vlanIp6Cidr) : ipv6Range; + String r2 = StringUtils.isEmpty(vlan.getIp6Range()) ? NetUtils.getIpv6RangeFromCidr(vlanIp6Cidr) : vlan.getIp6Range(); + if(NetUtils.isIp6RangeOverlap(r1, r2)) { + throw new InvalidParameterValueException(String.format("The IPv6 range with tag: %s already has IPs that overlap with the new range.", + vlan.getVlanTag())); } } + if (NetUtils.isSameIsolationId(vlanId, vlan.getVlanTag()) && !vlanIp6Gateway.equals(vlan.getIp6Gateway())) { + throw new InvalidParameterValueException(String.format("The IP range with tag: %s has already been added with gateway %s. Please specify a different tag.", + vlan.getVlanTag(), vlan.getIp6Gateway())); + } } } @@ -4829,44 +4927,48 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final Boolean forSystemVms) { final List listAllocatedIPs = _ipv6Dao.listByVlanIdAndState(id, IpAddress.State.Allocated); - if (ip6Gateway != null && !ip6Gateway.equals(vlanRange.getIp6Gateway()) && CollectionUtils.isNotEmpty(listAllocatedIPs)) { + if (ip6Gateway != null && !ip6Gateway.equals(vlanRange.getIp6Gateway()) && (CollectionUtils.isNotEmpty(listAllocatedIPs) || CollectionUtils.isNotEmpty(ipv6Service.getAllocatedIpv6FromVlanRange(vlanRange)))) { throw new InvalidParameterValueException(String.format("Unable to change ipv6 gateway to %s because some IPs are in use", ip6Gateway)); } - if (ip6Cidr != null && !ip6Cidr.equals(vlanRange.getIp6Cidr()) && CollectionUtils.isNotEmpty(listAllocatedIPs)) { + if (ip6Cidr != null && !ip6Cidr.equals(vlanRange.getIp6Cidr()) && (CollectionUtils.isNotEmpty(listAllocatedIPs) || CollectionUtils.isNotEmpty(ipv6Service.getAllocatedIpv6FromVlanRange(vlanRange)))) { throw new InvalidParameterValueException(String.format("Unable to change ipv6 cidr to %s because some IPs are in use", ip6Cidr)); } ip6Gateway = MoreObjects.firstNonNull(ip6Gateway, vlanRange.getIp6Gateway()); ip6Cidr = MoreObjects.firstNonNull(ip6Cidr, vlanRange.getIp6Cidr()); - final String[] existingVlanIPRangeArray = vlanRange.getIp6Range().split("-"); - final String currentStartIPv6 = existingVlanIPRangeArray[0]; - final String currentEndIPv6 = existingVlanIPRangeArray[1]; + final String[] existingVlanIPRangeArray = StringUtils.isNotEmpty(vlanRange.getIp6Range()) ? vlanRange.getIp6Range().split("-") : null; + final String currentStartIPv6 = existingVlanIPRangeArray != null ? existingVlanIPRangeArray[0] : null; + final String currentEndIPv6 = existingVlanIPRangeArray != null ? existingVlanIPRangeArray[1] : null; - startIpv6 = MoreObjects.firstNonNull(startIpv6, currentStartIPv6); - endIpv6 = MoreObjects.firstNonNull(endIpv6, currentEndIPv6); + startIpv6 = ObjectUtils.allNull(startIpv6, currentStartIPv6) ? null : MoreObjects.firstNonNull(startIpv6, currentStartIPv6); + endIpv6 = ObjectUtils.allNull(endIpv6, currentEndIPv6) ? null : MoreObjects.firstNonNull(endIpv6, currentEndIPv6); - if (startIpv6 != currentStartIPv6 || endIpv6 != currentEndIPv6) { - _networkModel.checkIp6Parameters(startIpv6, endIpv6, ip6Gateway, ip6Cidr); + _networkModel.checkIp6Parameters(startIpv6, endIpv6, ip6Gateway, ip6Cidr); + + if (!ObjectUtils.allNull(startIpv6, endIpv6) && ObjectUtils.anyNull(startIpv6, endIpv6)) { + throw new InvalidParameterValueException(String.format("Invalid IPv6 range %s-%s", startIpv6, endIpv6)); + } + if (ObjectUtils.allNotNull(startIpv6, endIpv6) && (!startIpv6.equals(currentStartIPv6) || !endIpv6.equals(currentEndIPv6))) { checkAllocatedIpv6sAreWithinVlanRange(listAllocatedIPs, startIpv6, endIpv6); + } - try { - VlanVO range = _vlanDao.acquireInLockTable(id, 30); - if (range == null) { - throw new CloudRuntimeException("Unable to acquire vlan configuration: " + id); - } - - if (s_logger.isDebugEnabled()) { - s_logger.debug("lock vlan " + id + " is acquired"); - } - - commitUpdateVlanAndIpRange(id, startIpv6, endIpv6, currentStartIPv6, currentEndIPv6, ip6Gateway, ip6Cidr, false, isRangeForSystemVM,forSystemVms); - - } catch (final Exception e) { - s_logger.error("Unable to edit VlanRange due to " + e.getMessage(), e); - throw new CloudRuntimeException("Failed to edit VlanRange. Please contact Cloud Support."); - } finally { - _vlanDao.releaseFromLockTable(id); + try { + VlanVO range = _vlanDao.acquireInLockTable(id, 30); + if (range == null) { + throw new CloudRuntimeException("Unable to acquire vlan configuration: " + id); } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("lock vlan " + id + " is acquired"); + } + + commitUpdateVlanAndIpRange(id, startIpv6, endIpv6, currentStartIPv6, currentEndIPv6, ip6Gateway, ip6Cidr, false, isRangeForSystemVM,forSystemVms); + + } catch (final Exception e) { + s_logger.error("Unable to edit VlanRange due to " + e.getMessage(), e); + throw new CloudRuntimeException("Failed to edit VlanRange. Please contact Cloud Support."); + } finally { + _vlanDao.releaseFromLockTable(id); } } @@ -4888,7 +4990,11 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new CloudRuntimeException("Failed to update IPv4 range. Please contact Cloud Support."); } } else { - vlanRange.setIp6Range(newStartIP + "-" + newEndIP); + if (ObjectUtils.allNotNull(newStartIP, newEndIP)) { + vlanRange.setIp6Range(newStartIP + "-" + newEndIP); + } else { + vlanRange.setIp6Range(null); + } vlanRange.setIp6Gateway(gateway); vlanRange.setIp6Cidr(netmask); _vlanDao.update(vlanRange.getId(), vlanRange); @@ -4900,6 +5006,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati private boolean checkIfVlanRangeIsForSystemVM(final long vlanId) { List existingPublicIPs = _publicIpAddressDao.listByVlanId(vlanId); + if (CollectionUtils.isEmpty(existingPublicIPs)) { + return false; + } boolean initialIsSystemVmValue = existingPublicIPs.get(0).isForSystemVms(); for (IPAddressVO existingIPs : existingPublicIPs) { if (initialIsSystemVmValue != existingIPs.isForSystemVms()) { @@ -5033,6 +5142,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException(allocIpCount + " Ips are in use. Cannot delete this vlan"); } } + List ipAddresses = ipv6Service.getAllocatedIpv6FromVlanRange(vlanRange); + if (CollectionUtils.isNotEmpty(ipAddresses)) { + throw new InvalidParameterValueException(String.format("%d IPv6 addresses are in use. Cannot delete this vlan", ipAddresses.size())); + } Transaction.execute(new TransactionCallbackNoReturn() { @Override @@ -5603,6 +5716,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati public NetworkOffering createNetworkOffering(final CreateNetworkOfferingCmd cmd) { final String name = cmd.getNetworkOfferingName(); final String displayText = cmd.getDisplayText(); + final NetUtils.InternetProtocol internetProtocol = NetUtils.InternetProtocol.fromValue(cmd.getInternetProtocol()); final String tags = cmd.getTags(); final String trafficTypeString = cmd.getTraffictype(); final boolean specifyVlan = cmd.getSpecifyVlan(); @@ -5668,6 +5782,16 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException("Invalid \"type\" parameter is given; can have Shared and Isolated values"); } + if (internetProtocol != null) { + if (!GuestType.Isolated.equals(guestType)) { + throw new InvalidParameterValueException(String.format("%s is supported only for %s guest type", ApiConstants.INTERNET_PROTOCOL, GuestType.Isolated)); + } + + if (!Ipv6Service.Ipv6OfferingCreationEnabled.value() && !NetUtils.InternetProtocol.IPv4.equals(internetProtocol)) { + throw new InvalidParameterValueException(String.format("Configuration %s needs to be enabled for creating IPv6 supported network offering", Ipv6Service.Ipv6OfferingCreationEnabled.key())); + } + } + // Verify availability for (final Availability avlb : Availability.values()) { if (avlb.name().equalsIgnoreCase(availabilityStr)) { @@ -5896,7 +6020,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } final NetworkOfferingVO offering = createNetworkOffering(name, displayText, trafficType, tags, specifyVlan, availability, networkRate, serviceProviderMap, false, guestType, false, - serviceOfferingId, conserveMode, serviceCapabilityMap, specifyIpRanges, isPersistent, details, egressDefaultPolicy, maxconn, enableKeepAlive, forVpc, domainIds, zoneIds, enable); + serviceOfferingId, conserveMode, serviceCapabilityMap, specifyIpRanges, isPersistent, details, egressDefaultPolicy, maxconn, enableKeepAlive, forVpc, domainIds, zoneIds, enable, internetProtocol); CallContext.current().setEventDetails(" Id: " + offering.getId() + " Name: " + name); CallContext.current().putContextParameter(NetworkOffering.class, offering.getId()); return offering; @@ -6035,7 +6159,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final Long serviceOfferingId, final boolean conserveMode, final Map> serviceCapabilityMap, final boolean specifyIpRanges, final boolean isPersistent, final Map details, final boolean egressDefaultPolicy, final Integer maxconn, final boolean enableKeepAlive, Boolean forVpc, - final List domainIds, final List zoneIds, final boolean enableOffering) { + final List domainIds, final List zoneIds, final boolean enableOffering, final NetUtils.InternetProtocol internetProtocol) { String servicePackageUuid; String spDescription = null; @@ -6280,6 +6404,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati detailsVO.add(new NetworkOfferingDetailsVO(offering.getId(), Detail.zoneid, String.valueOf(zoneId), false)); } } + if (internetProtocol != null) { + detailsVO.add(new NetworkOfferingDetailsVO(offering.getId(), Detail.internetProtocol, String.valueOf(internetProtocol), true)); + } if (!detailsVO.isEmpty()) { networkOfferingDetailsDao.saveDetails(detailsVO); } diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java index e99b2fd6757..fe37e670306 100644 --- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java @@ -137,6 +137,7 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis to.setName(profile.getName()); to.setSecurityGroupEnabled(profile.isSecurityGroupEnabled()); to.setIp6Address(profile.getIPv6Address()); + to.setIp6Gateway(profile.getIPv6Gateway()); to.setIp6Cidr(profile.getIPv6Cidr()); NetworkVO network = _networkDao.findById(profile.getNetworkId()); diff --git a/server/src/main/java/com/cloud/network/ExternalFirewallDeviceManagerImpl.java b/server/src/main/java/com/cloud/network/ExternalFirewallDeviceManagerImpl.java index 496359d1d07..21eae27ea66 100644 --- a/server/src/main/java/com/cloud/network/ExternalFirewallDeviceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/ExternalFirewallDeviceManagerImpl.java @@ -544,7 +544,7 @@ public abstract class ExternalFirewallDeviceManagerImpl extends AdapterBase impl if (!add) { List nics = _nicDao.listByNetworkId(network.getId()); for (NicVO nic : nics) { - if (nic.getVmType() == null && nic.getReservationStrategy().equals(ReservationStrategy.PlaceHolder) && nic.getIPv4Address().equals(network.getGateway())) { + if (nic.getVmType() == null && ReservationStrategy.PlaceHolder.equals(nic.getReservationStrategy()) && nic.getIPv4Address().equals(network.getGateway())) { s_logger.debug("Removing placeholder nic " + nic + " for the network " + network); _nicDao.remove(nic.getId()); } diff --git a/server/src/main/java/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java b/server/src/main/java/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java index 2ee34180b7a..b015eedf9ed 100644 --- a/server/src/main/java/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/ExternalLoadBalancerDeviceManagerImpl.java @@ -1300,7 +1300,7 @@ public abstract class ExternalLoadBalancerDeviceManagerImpl extends AdapterBase List guestIps = _nicDao.listByNetworkId(network.getId()); for (NicVO guestIp : guestIps) { // only external firewall and external load balancer will create NicVO with PlaceHolder reservation strategy - if (guestIp.getReservationStrategy().equals(ReservationStrategy.PlaceHolder) && guestIp.getVmType() == null && guestIp.getReserver() == null && + if (ReservationStrategy.PlaceHolder.equals(guestIp.getReservationStrategy()) && guestIp.getVmType() == null && guestIp.getReserver() == null && !guestIp.getIPv4Address().equals(network.getGateway())) { return guestIp; } diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java index ccba7f54b52..7ec4d0fd37e 100644 --- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java @@ -27,6 +27,7 @@ import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.Collections; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -46,6 +47,7 @@ import org.apache.cloudstack.region.PortableIp; import org.apache.cloudstack.region.PortableIpDao; import org.apache.cloudstack.region.PortableIpVO; import org.apache.cloudstack.region.Region; +import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; @@ -131,6 +133,7 @@ import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.PrivateIpDao; import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.network.vpc.dao.VpcOfferingDao; import com.cloud.network.vpn.RemoteAccessVpnService; import com.cloud.offering.NetworkOffering; import com.cloud.offering.NetworkOffering.Availability; @@ -293,6 +296,8 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage @Inject VpcDao _vpcDao; @Inject + VpcOfferingDao vpcOfferingDao; + @Inject DataCenterIpAddressDao _privateIPAddressDao; @Inject HostPodDao _hpDao; @@ -311,6 +316,17 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage private Random rand = new Random(System.currentTimeMillis()); + private List getIpv6SupportingVlanRangeIds(long dcId) throws InsufficientAddressCapacityException { + List vlans = _vlanDao.listIpv6SupportingVlansByZone(dcId); + if (CollectionUtils.isEmpty(vlans)) { + s_logger.error("Unable to find VLAN IP range that support both IPv4 and IPv6"); + InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Insufficient address capacity", DataCenter.class, dcId); + ex.addProxyObject(ApiDBUtils.findZoneById(dcId).getUuid()); + throw ex; + } + return vlans.stream().map(VlanVO::getId).collect(Collectors.toList()); + } + @DB private IPAddressVO assignAndAllocateIpAddressEntry(final Account owner, final VlanType vlanUse, final Long guestNetworkId, final boolean sourceNat, final boolean allocate, final boolean isSystem, @@ -1035,15 +1051,22 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage if (s_logger.isDebugEnabled()) { s_logger.debug("lock account " + ownerId + " is acquired"); } + List vlanDbIds = null; boolean displayIp = true; if (guestNtwkId != null) { Network ntwk = _networksDao.findById(guestNtwkId); + if (_networkOfferingDao.isIpv6Supported(ntwk.getNetworkOfferingId())) { + vlanDbIds = getIpv6SupportingVlanRangeIds(dcId); + } displayIp = ntwk.getDisplayNetwork(); } else if (vpcId != null) { VpcVO vpc = _vpcDao.findById(vpcId); + if (vpcOfferingDao.isIpv6Supported(vpc.getVpcOfferingId())) { + vlanDbIds = getIpv6SupportingVlanRangeIds(dcId); + } displayIp = vpc.isDisplay(); } - return fetchNewPublicIp(dcId, null, null, owner, VlanType.VirtualNetwork, guestNtwkId, isSourceNat, true, null, null, false, vpcId, displayIp, false); + return fetchNewPublicIp(dcId, null, vlanDbIds, owner, VlanType.VirtualNetwork, guestNtwkId, isSourceNat, true, null, null, false, vpcId, displayIp, false); } }); if (ip.getState() != State.Allocated) { diff --git a/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java b/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java index c1f3f39dced..2a53708fc59 100644 --- a/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/Ipv6AddressManagerImpl.java @@ -22,29 +22,33 @@ import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.vm.NicProfile; -import com.googlecode.ipv6.IPv6Address; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.log4j.Logger; import com.cloud.configuration.Config; import com.cloud.dc.DataCenter; +import com.cloud.dc.Vlan; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.VlanDao; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; 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.NetworkDetailsDao; 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.net.NetUtils; +import com.cloud.vm.NicProfile; import com.cloud.vm.dao.NicSecondaryIpDao; import com.cloud.vm.dao.NicSecondaryIpVO; +import com.googlecode.ipv6.IPv6Address; public class Ipv6AddressManagerImpl extends ManagerBase implements Ipv6AddressManager { public static final Logger s_logger = Logger.getLogger(Ipv6AddressManagerImpl.class.getName()); @@ -68,6 +72,8 @@ public class Ipv6AddressManagerImpl extends ManagerBase implements Ipv6AddressMa NicSecondaryIpDao nicSecondaryIpDao; @Inject IPAddressDao ipAddressDao; + @Inject + NetworkDetailsDao networkDetailsDao; @Override public boolean configure(String name, Map params) throws ConfigurationException { @@ -213,6 +219,12 @@ public class Ipv6AddressManagerImpl extends ManagerBase implements Ipv6AddressMa } else { nic.setFormat(Networks.AddressFormat.Ip6); } + if (Network.GuestType.Isolated.equals(network.getGuestType())) { + final boolean usageHidden = networkDetailsDao.isNetworkUsageHidden(network.getId()); + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_IP6_ASSIGN, network.getAccountId(), network.getDataCenterId(), 0L, + ipv6addr.toString(), false, Vlan.VlanType.VirtualNetwork.toString(), false, usageHidden, + IPv6Address.class.getName(), null); + } } nic.setIPv6Dns1(dc.getIp6Dns1()); nic.setIPv6Dns2(dc.getIp6Dns2()); diff --git a/server/src/main/java/com/cloud/network/Ipv6ServiceImpl.java b/server/src/main/java/com/cloud/network/Ipv6ServiceImpl.java new file mode 100644 index 00000000000..72e81419e64 --- /dev/null +++ b/server/src/main/java/com/cloud/network/Ipv6ServiceImpl.java @@ -0,0 +1,708 @@ +// 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 java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd; +import org.apache.cloudstack.api.command.admin.network.DeleteGuestNetworkIpv6PrefixCmd; +import org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6PrefixesCmd; +import org.apache.cloudstack.api.command.user.ipv6.CreateIpv6FirewallRuleCmd; +import org.apache.cloudstack.api.command.user.ipv6.DeleteIpv6FirewallRuleCmd; +import org.apache.cloudstack.api.command.user.ipv6.ListIpv6FirewallRulesCmd; +import org.apache.cloudstack.api.command.user.ipv6.UpdateIpv6FirewallRuleCmd; +import org.apache.cloudstack.api.response.Ipv6RouteResponse; +import org.apache.cloudstack.api.response.VpcResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import com.cloud.api.ApiDBUtils; +import com.cloud.configuration.Resource; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterGuestIpv6Prefix; +import com.cloud.dc.DataCenterGuestIpv6PrefixVO; +import com.cloud.dc.Vlan; +import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.DataCenterGuestIpv6PrefixDao; +import com.cloud.dc.dao.VlanDao; +import com.cloud.event.ActionEvent; +import com.cloud.event.ActionEventUtils; +import com.cloud.event.EventTypes; +import com.cloud.event.EventVO; +import com.cloud.event.UsageEventUtils; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao; +import com.cloud.network.dao.NetworkDetailsDao; +import com.cloud.network.firewall.FirewallService; +import com.cloud.network.guru.PublicNetworkGuru; +import com.cloud.network.rules.FirewallManager; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.FirewallRuleVO; +import com.cloud.network.vpc.Vpc; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ComponentLifecycleBase; +import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.db.GlobalLock; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionCallbackWithException; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.Nic; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.NicDao; +import com.googlecode.ipv6.IPv6Address; +import com.googlecode.ipv6.IPv6Network; +import com.googlecode.ipv6.IPv6NetworkMask; + +public class Ipv6ServiceImpl extends ComponentLifecycleBase implements Ipv6Service { + + public static final Logger s_logger = Logger.getLogger(Ipv6ServiceImpl.class.getName()); + private static final String s_publicNetworkReserver = PublicNetworkGuru.class.getSimpleName(); + + ScheduledExecutorService _ipv6GuestPrefixSubnetNetworkMapStateScanner; + + @Inject + NetworkOfferingDao networkOfferingDao; + @Inject + VlanDao vlanDao; + @Inject + DataCenterGuestIpv6PrefixDao dataCenterGuestIpv6PrefixDao; + @Inject + Ipv6GuestPrefixSubnetNetworkMapDao ipv6GuestPrefixSubnetNetworkMapDao; + @Inject + FirewallRulesDao firewallDao; + @Inject + FirewallService firewallService; + @Inject + NetworkDetailsDao networkDetailsDao; + @Inject + NicDao nicDao; + @Inject + DomainRouterDao domainRouterDao; + @Inject + AccountManager accountManager; + @Inject + NetworkModel networkModel; + @Inject + IPAddressDao ipAddressDao; + @Inject + FirewallManager firewallManager; + @Inject + NetworkOrchestrationService networkOrchestrationService; + + private boolean isPublicIpv6PlaceholderNic(NicVO nic) { + return ObjectUtils.allNotNull(nic.getIPv6Address(), nic.getIPv6Cidr(), nic.getIPv6Gateway()) && + s_publicNetworkReserver.equals(nic.getReserver()); + } + + private Pair getPublicIpv6FromNetworkPlaceholder(Network network, List ranges) { + List placeholderNics = nicDao.listPlaceholderNicsByNetworkIdAndVmType(network.getId(), VirtualMachine.Type.DomainRouter); + if (CollectionUtils.isEmpty(placeholderNics)) { + return null; + } + Optional nicOptional = placeholderNics.stream().filter(this::isPublicIpv6PlaceholderNic).findFirst(); + if (nicOptional.isEmpty()) { + return null; + } + NicVO nic = nicOptional.get(); + Optional vlanOptional = ranges.stream().filter(v -> nic.getIPv6Cidr().equals(v.getIp6Cidr()) && nic.getIPv6Gateway().equals(v.getIp6Gateway())).findFirst(); + if (vlanOptional.isEmpty()) { + s_logger.error(String.format("Public IPv6 placeholder NIC with cidr: %s, gateway: %s for network ID: %d is not present in the allocated VLAN: %s", + nic.getIPv6Cidr(), nic.getIPv6Gateway(),network.getId(), ranges.get(0).getVlanTag())); + return null; + } + return new Pair<>(nic.getIPv6Address(), vlanOptional.get()); + } + + private Pair assignPublicIpv6ToNetworkInternal(Network network, String vlanId, String nicMacAddress) throws InsufficientAddressCapacityException { + Pair result = Transaction.execute((TransactionCallbackWithException, InsufficientAddressCapacityException>) status -> { + final List ranges = vlanDao.listIpv6RangeByPhysicalNetworkIdAndVlanId(network.getPhysicalNetworkId(), vlanId); + if (CollectionUtils.isEmpty(ranges)) { + s_logger.error(String.format("Unable to find IPv6 address for zone ID: %d, physical network ID: %d, VLAN: %s", network.getDataCenterId(), network.getPhysicalNetworkId(), vlanId)); + InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Insufficient address capacity", DataCenter.class, network.getDataCenterId()); + ex.addProxyObject(ApiDBUtils.findZoneById(network.getDataCenterId()).getUuid()); + throw ex; + } + Pair placeholderResult = getPublicIpv6FromNetworkPlaceholder(network, ranges); + if (placeholderResult != null) { + return placeholderResult; + } + removePublicIpv6PlaceholderNics(network); + Collections.shuffle(ranges); + VlanVO selectedVlan = ranges.get(0); + IPv6Network ipv6Network = IPv6Network.fromString(selectedVlan.getIp6Cidr()); + if (ipv6Network.getNetmask().asPrefixLength() < IPV6_SLAAC_CIDR_NETMASK) { + Iterator splits = ipv6Network.split(IPv6NetworkMask.fromPrefixLength(IPV6_SLAAC_CIDR_NETMASK)); + if (splits.hasNext()) { + ipv6Network = splits.next(); + } + } + IPv6Address ipv6Addr = NetUtils.EUI64Address(ipv6Network, nicMacAddress); + networkOrchestrationService.savePlaceholderNic(network, null, ipv6Addr.toString(), selectedVlan.getIp6Cidr(), selectedVlan.getIp6Gateway(), s_publicNetworkReserver, VirtualMachine.Type.DomainRouter); + return new Pair<>(ipv6Addr.toString(), selectedVlan); + }); + final String ipv6Address = result.first(); + final String event = EventTypes.EVENT_NET_IP6_ASSIGN; + final String description = String.format("Assigned public IPv6 address: %s for network ID: %s", ipv6Address, network.getUuid()); + ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), network.getAccountId(), EventVO.LEVEL_INFO, event, description, network.getId(), ApiCommandResourceType.Network.toString(), 0); + final boolean usageHidden = networkDetailsDao.isNetworkUsageHidden(network.getId()); + final String guestType = result.second().getVlanType().toString(); + UsageEventUtils.publishUsageEvent(event, network.getAccountId(), network.getDataCenterId(), 0L, + ipv6Address, false, guestType, false, usageHidden, + IPv6Network.class.getName(), null); + return result; + } + + private Ipv6GuestPrefixSubnetNetworkMapVO preallocatePrefixSubnetRandomly(DataCenterGuestIpv6PrefixVO prefix) { + Ipv6GuestPrefixSubnetNetworkMapVO ip6Subnet = null; + List prefixSubnetNetworkMapVOList = ipv6GuestPrefixSubnetNetworkMapDao.listUsedByPrefix(prefix.getId()); + List usedPrefixList = prefixSubnetNetworkMapVOList.stream().map(Ipv6GuestPrefixSubnetNetworkMap::getSubnet).collect(Collectors.toList()); + final IPv6Network ip6Prefix = IPv6Network.fromString(prefix.getPrefix()); + Iterator splits = ip6Prefix.split(IPv6NetworkMask.fromPrefixLength(IPV6_SLAAC_CIDR_NETMASK)); + List availableSubnets = new ArrayList<>(); + while (splits.hasNext()) { + IPv6Network i = splits.next(); + if (!usedPrefixList.contains(i.toString())) { + availableSubnets.add(i); + } + } + if (CollectionUtils.isNotEmpty(availableSubnets)) { + Random r = new Random(); + IPv6Network subnet = availableSubnets.get(r.nextInt(availableSubnets.size())); + ip6Subnet = new Ipv6GuestPrefixSubnetNetworkMapVO(prefix.getId(), subnet.toString(), null, Ipv6GuestPrefixSubnetNetworkMap.State.Allocating); + } + return ip6Subnet; + } + + protected void releaseIpv6Subnet(long subnetId) { + Ipv6GuestPrefixSubnetNetworkMapVO ipv6GuestPrefixSubnetNetworkMapVO = ipv6GuestPrefixSubnetNetworkMapDao.createForUpdate(subnetId); + ipv6GuestPrefixSubnetNetworkMapVO.setState(Ipv6GuestPrefixSubnetNetworkMap.State.Free); + ipv6GuestPrefixSubnetNetworkMapVO.setNetworkId(null); + ipv6GuestPrefixSubnetNetworkMapVO.setUpdated(new Date()); + ipv6GuestPrefixSubnetNetworkMapDao.update(ipv6GuestPrefixSubnetNetworkMapVO.getId(), ipv6GuestPrefixSubnetNetworkMapVO); + } + + @Override + public boolean start() { + _ipv6GuestPrefixSubnetNetworkMapStateScanner.scheduleWithFixedDelay(new Ipv6GuestPrefixSubnetNetworkMapStateScanner(), 300, 30*60, TimeUnit.SECONDS); + return true; + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _name = name; + _configParams = params; + _ipv6GuestPrefixSubnetNetworkMapStateScanner = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Ipv6GuestPrefixSubnet-State-Scanner")); + + return true; + } + + @Override + public List> getCommands() { + final List> cmdList = new ArrayList>(); + cmdList.add(CreateGuestNetworkIpv6PrefixCmd.class); + cmdList.add(ListGuestNetworkIpv6PrefixesCmd.class); + cmdList.add(DeleteGuestNetworkIpv6PrefixCmd.class); + cmdList.add(CreateIpv6FirewallRuleCmd.class); + cmdList.add(ListIpv6FirewallRulesCmd.class); + cmdList.add(UpdateIpv6FirewallRuleCmd.class); + cmdList.add(DeleteIpv6FirewallRuleCmd.class); + return cmdList; + } + + @Override + public String getConfigComponentName() { + return Ipv6Service.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] { + Ipv6OfferingCreationEnabled, + Ipv6PrefixSubnetCleanupInterval + }; + } + + @Override + public Pair getUsedTotalIpv6SubnetForPrefix(DataCenterGuestIpv6Prefix prefix) { + List usedSubnets = ipv6GuestPrefixSubnetNetworkMapDao.listUsedByPrefix(prefix.getId()); + final IPv6Network ip6Prefix = IPv6Network.fromString(prefix.getPrefix()); + Iterator splits = ip6Prefix.split(IPv6NetworkMask.fromPrefixLength(IPV6_SLAAC_CIDR_NETMASK)); + int total = 0; + while(splits.hasNext()) { + total++; + splits.next(); + } + return new Pair<>(usedSubnets.size(), total); + } + + @Override + public Pair getUsedTotalIpv6SubnetForZone(long zoneId) { + int used = 0; + int total = 0; + List prefixes = dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId); + for (DataCenterGuestIpv6PrefixVO prefix : prefixes) { + Pair usedTotal = getUsedTotalIpv6SubnetForPrefix(prefix); + used += usedTotal.first(); + total += usedTotal.second(); + } + return new Pair<>(used, total); + } + + public Pair preAllocateIpv6SubnetForNetwork(long zoneId) throws ResourceAllocationException { + return Transaction.execute((TransactionCallbackWithException, ResourceAllocationException>) status -> { + List prefixes = dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId); + if (CollectionUtils.isEmpty(prefixes)) { + s_logger.error(String.format("IPv6 prefixes not found for the zone ID: %d", zoneId)); + throw new ResourceAllocationException("Unable to allocate IPv6 network", Resource.ResourceType.network); + } + Ipv6GuestPrefixSubnetNetworkMapVO ip6Subnet = null; + for (DataCenterGuestIpv6PrefixVO prefix : prefixes) { + ip6Subnet = ipv6GuestPrefixSubnetNetworkMapDao.findFirstAvailable(prefix.getId()); + if (ip6Subnet == null) { + ip6Subnet = preallocatePrefixSubnetRandomly(prefix); + } + if (ip6Subnet != null) { + break; + } + } + if (ip6Subnet == null) { + throw new ResourceAllocationException("Unable to allocate IPv6 guest subnet for the network", Resource.ResourceType.network); + } + ip6Subnet.setUpdated(new Date()); + if (Ipv6GuestPrefixSubnetNetworkMap.State.Free.equals(ip6Subnet.getState())) { + ip6Subnet.setState(Ipv6GuestPrefixSubnetNetworkMap.State.Allocating); + ipv6GuestPrefixSubnetNetworkMapDao.update(ip6Subnet.getId(), ip6Subnet); + } else { + ipv6GuestPrefixSubnetNetworkMapDao.persist(ip6Subnet); + } + IPv6Network network = IPv6Network.fromString(ip6Subnet.getSubnet()); + IPv6Address gateway = network.getFirst().add(1); + return new Pair<>(gateway.toString(), network.toString()); + }); + } + + @Override + public void assignIpv6SubnetToNetwork(String subnet, long networkId) { + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + Ipv6GuestPrefixSubnetNetworkMapVO ipv6GuestPrefixSubnetNetworkMapVO = ipv6GuestPrefixSubnetNetworkMapDao.findBySubnet(subnet); + if (ipv6GuestPrefixSubnetNetworkMapVO != null) { + ipv6GuestPrefixSubnetNetworkMapVO = ipv6GuestPrefixSubnetNetworkMapDao.createForUpdate(ipv6GuestPrefixSubnetNetworkMapVO.getId()); + ipv6GuestPrefixSubnetNetworkMapVO.setState(Ipv6GuestPrefixSubnetNetworkMap.State.Allocated); + ipv6GuestPrefixSubnetNetworkMapVO.setNetworkId(networkId); + ipv6GuestPrefixSubnetNetworkMapVO.setUpdated(new Date()); + ipv6GuestPrefixSubnetNetworkMapDao.update(ipv6GuestPrefixSubnetNetworkMapVO.getId(), ipv6GuestPrefixSubnetNetworkMapVO); + } + } + }); + } + + @Override + public void releaseIpv6SubnetForNetwork(long networkId) { + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + Ipv6GuestPrefixSubnetNetworkMapVO ipv6GuestPrefixSubnetNetworkMapVO = ipv6GuestPrefixSubnetNetworkMapDao.findByNetworkId(networkId); + if (ipv6GuestPrefixSubnetNetworkMapVO != null) { + releaseIpv6Subnet(ipv6GuestPrefixSubnetNetworkMapVO.getId()); + } + } + }); + } + + @Override + public List getAllocatedIpv6FromVlanRange(Vlan vlan) { + if (ObjectUtils.allNull(vlan.getIp6Cidr(), vlan.getIp6Gateway())) { + return null; + } + List nics = nicDao.findNicsByIpv6GatewayIpv6CidrAndReserver(vlan.getIp6Gateway(), vlan.getIp6Cidr(), s_publicNetworkReserver); + if (CollectionUtils.isNotEmpty(nics)) { + return nics.stream().map(NicVO::getIPv6Address).collect(Collectors.toList()); + } + return null; + } + + @Override + public Nic assignPublicIpv6ToNetwork(Network network, Nic nic) { + if (StringUtils.isNotEmpty(nic.getIPv6Address())) { + return nic; + } + try { + Pair publicIpv6AddressVlanPair = assignPublicIpv6ToNetworkInternal(network, nic.getBroadcastUri().toString(), nic.getMacAddress()); + Vlan vlan = publicIpv6AddressVlanPair.second(); + final String ipv6Address = publicIpv6AddressVlanPair.first(); + final String ipv6Gateway = vlan.getIp6Gateway(); + final String ipv6Cidr = vlan.getIp6Cidr(); + NicVO nicVO = nicDao.createForUpdate(nic.getId()); + nicVO.setIPv6Address(ipv6Address); + nicVO.setIPv6Gateway(ipv6Gateway); + nicVO.setIPv6Cidr(ipv6Cidr); + nicDao.update(nic.getId(), nicVO); + return nicVO; + } catch (InsufficientAddressCapacityException ex) { + throw new CloudRuntimeException(ex.getMessage()); + } + } + + @Override + public void updateNicIpv6(NicProfile nic, DataCenter dc, Network network) throws InsufficientAddressCapacityException { + boolean isIpv6Supported = networkOfferingDao.isIpv6Supported(network.getNetworkOfferingId()); + if (nic.getIPv6Address() == null && isIpv6Supported) { + Pair publicIpv6AddressVlanPair = assignPublicIpv6ToNetworkInternal(network, nic.getBroadCastUri().toString(), nic.getMacAddress()); + final Vlan vlan = publicIpv6AddressVlanPair.second(); + final String routerIpv6 = publicIpv6AddressVlanPair.first(); + final String routerIpv6Gateway = vlan.getIp6Gateway(); + final String routerIpv6Cidr = vlan.getIp6Cidr(); + nic.setIPv6Address(routerIpv6); + nic.setIPv6Gateway(routerIpv6Gateway); + nic.setIPv6Cidr(routerIpv6Cidr); + if (nic.getIPv4Address() != null) { + nic.setFormat(Networks.AddressFormat.DualStack); + } else { + nic.setFormat(Networks.AddressFormat.Ip6); + } + nic.setIPv6Dns1(dc.getIp6Dns1()); + nic.setIPv6Dns2(dc.getIp6Dns2()); + } + } + + @Override + public void releasePublicIpv6ForNic(Network network, String nicIpv6Address) { + String event = EventTypes.EVENT_NET_IP6_RELEASE; + String description = String.format("Releasing public IPv6 address: %s from network ID: %s", nicIpv6Address, network.getUuid()); + ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), network.getAccountId(), EventVO.LEVEL_INFO, event, description, network.getId(), ApiCommandResourceType.Network.toString(), 0); + final boolean usageHidden = networkDetailsDao.isNetworkUsageHidden(network.getId()); + UsageEventUtils.publishUsageEvent(event, network.getAccountId(), network.getDataCenterId(), 0L, + nicIpv6Address, false, Vlan.VlanType.VirtualNetwork.toString(), false, usageHidden, + IPv6Address.class.getName(), null); + } + + @Override + public List getPublicIpv6AddressesForNetwork(Network network) { + List addresses = new ArrayList<>(); + List routers = domainRouterDao.findByNetwork(network.getId()); + for (DomainRouterVO router : routers) { + List nics = nicDao.listByVmId(router.getId()); + for (NicVO nic : nics) { + String address = nic.getIPv6Address(); + if (!s_publicNetworkReserver.equals(nic.getReserver()) || + StringUtils.isEmpty(address) || + addresses.contains(address)) { + continue; + } + addresses.add(address); + } + } + return addresses; + } + + @Override + public void updateIpv6RoutesForVpcResponse(Vpc vpc, VpcResponse response) { + Set ipv6Routes = new LinkedHashSet<>(); + List networks = networkModel.listNetworksByVpc(vpc.getId()); + for (Network network : networks) { + if (networkOfferingDao.isIpv6Supported(network.getNetworkOfferingId())) { + List networkPublicIpv6 = getPublicIpv6AddressesForNetwork(network); + for (String address : networkPublicIpv6) { + Ipv6RouteResponse route = new Ipv6RouteResponse(network.getIp6Cidr(), address); + ipv6Routes.add(route); + } + } + } + if (CollectionUtils.isNotEmpty(ipv6Routes)) { + response.setIpv6Routes(ipv6Routes); + } + } + + @Override + public void checkNetworkIpv6Upgrade(Network network) throws InsufficientAddressCapacityException, ResourceAllocationException { + List prefixes = dataCenterGuestIpv6PrefixDao.listByDataCenterId(network.getDataCenterId()); + if (CollectionUtils.isEmpty(prefixes)) { + s_logger.error(String.format("IPv6 prefixes not found for the zone ID: %d", network.getDataCenterId())); + throw new ResourceAllocationException("Unable to allocate IPv6 network", Resource.ResourceType.network); + } + List addresses = network.getVpcId() == null ? + ipAddressDao.listByAssociatedNetwork(network.getId(), true) : + ipAddressDao.listByAssociatedVpc(network.getVpcId(), true); + for (IPAddressVO address : addresses) { + VlanVO vlan = vlanDao.findById(address.getVlanId()); + final List ranges = vlanDao.listIpv6RangeByPhysicalNetworkIdAndVlanId(network.getPhysicalNetworkId(), vlan.getVlanTag()); + if (CollectionUtils.isEmpty(ranges)) { + s_logger.error(String.format("Unable to find IPv6 address for zone ID: %d, physical network ID: %d, VLAN: %s", network.getDataCenterId(), network.getPhysicalNetworkId(), vlan.getVlanTag())); + InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Insufficient address capacity", DataCenter.class, network.getDataCenterId()); + ex.addProxyObject(ApiDBUtils.findZoneById(network.getDataCenterId()).getUuid()); + throw ex; + } + } + } + + @Override + public Pair, Integer> listIpv6FirewallRules(ListIpv6FirewallRulesCmd listIpv6FirewallRulesCmd) { + return firewallService.listFirewallRules(listIpv6FirewallRulesCmd); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_OPEN, eventDescription = "creating IPv6 firewall rule", create = true) + public FirewallRule createIpv6FirewallRule(CreateIpv6FirewallRuleCmd cmd) throws NetworkRuleConflictException { + final Account caller = CallContext.current().getCallingAccount(); + final long networkId = cmd.getNetworkId(); + final Integer portStart = cmd.getSourcePortStart(); + final Integer portEnd = cmd.getSourcePortEnd(); + final FirewallRule.TrafficType trafficType = cmd.getTrafficType(); + final String protocol = cmd.getProtocol(); + final Integer icmpCode = cmd.getIcmpCode(); + final Integer icmpType = cmd.getIcmpType(); + final boolean forDisplay = cmd.isDisplay(); + final FirewallRule.FirewallRuleType type = FirewallRule.FirewallRuleType.User; + final List sourceCidrList = cmd.getSourceCidrList(); + final List destinationCidrList = cmd.getDestinationCidrList(); + + for (String cidr : sourceCidrList) { + if (!NetUtils.isValidIp6Cidr(cidr)) { + throw new InvalidParameterValueException(String.format("Invalid source IPv6 CIDR: %s", cidr)); + } + } + for (String cidr : destinationCidrList) { + if (!NetUtils.isValidIp6Cidr(cidr)) { + throw new InvalidParameterValueException(String.format("Invalid destination IPv6 CIDR: %s", cidr)); + } + } + if (portStart != null && !NetUtils.isValidPort(portStart)) { + throw new InvalidParameterValueException("publicPort is an invalid value: " + portStart); + } + if (portEnd != null && !NetUtils.isValidPort(portEnd)) { + throw new InvalidParameterValueException("Public port range is an invalid value: " + portEnd); + } + if (ObjectUtils.allNotNull(portStart, portEnd) && portStart > portEnd) { + throw new InvalidParameterValueException("Start port can't be bigger than end port"); + } + + Network network = networkModel.getNetwork(networkId); + assert network != null : "Can't create rule as network is null?"; + + final long accountId = network.getAccountId(); + final long domainId = network.getDomainId(); + + if (FirewallRule.TrafficType.Egress.equals(trafficType)) { + accountManager.checkAccess(caller, null, true, network); + } + + // Verify that the network guru supports the protocol specified + Map caps = networkModel.getNetworkServiceCapabilities(network.getId(), Network.Service.Firewall); + + if (caps != null) { + String supportedProtocols; + String supportedTrafficTypes = null; + supportedTrafficTypes = caps.get(Network.Capability.SupportedTrafficDirection).toLowerCase(); + + if (trafficType == FirewallRule.TrafficType.Egress) { + supportedProtocols = caps.get(Network.Capability.SupportedEgressProtocols).toLowerCase(); + } else { + supportedProtocols = caps.get(Network.Capability.SupportedProtocols).toLowerCase(); + } + + if (!supportedProtocols.contains(protocol.toLowerCase())) { + throw new InvalidParameterValueException(String.format("Protocol %s is not supported in zone", protocol)); + } else if (!supportedTrafficTypes.contains(trafficType.toString().toLowerCase())) { + throw new InvalidParameterValueException("Traffic Type " + trafficType + " is currently supported by Firewall in network " + networkId); + } + } + + // icmp code and icmp type can't be passed in for any other protocol rather than icmp + if (!protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) && (icmpCode != null || icmpType != null)) { + throw new InvalidParameterValueException("Can specify icmpCode and icmpType for ICMP protocol only"); + } + + if (protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) && (portStart != null || portEnd != null)) { + throw new InvalidParameterValueException("Can't specify start/end port when protocol is ICMP"); + } + + return Transaction.execute(new TransactionCallbackWithException() { + @Override + public FirewallRuleVO doInTransaction(TransactionStatus status) throws NetworkRuleConflictException { + FirewallRuleVO newRule = + new FirewallRuleVO(null, null, portStart, portEnd, protocol.toLowerCase(), networkId, accountId, domainId, FirewallRule.Purpose.Ipv6Firewall, + sourceCidrList, destinationCidrList, icmpCode, icmpType, null, trafficType); + newRule.setType(type); + newRule.setDisplay(forDisplay); + newRule = firewallDao.persist(newRule); + + if (FirewallRule.FirewallRuleType.User.equals(type)) { + firewallManager.detectRulesConflict(newRule); + } + + if (!firewallDao.setStateToAdd(newRule)) { + throw new CloudRuntimeException("Unable to update the state to add for " + newRule); + } + CallContext.current().setEventDetails("Rule Id: " + newRule.getId()); + + return newRule; + } + }); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_CLOSE, eventDescription = "revoking IPv6 firewall rule", async = true) + public boolean revokeIpv6FirewallRule(Long id) { + FirewallRuleVO rule = firewallDao.findById(id); + if (rule == null) { + throw new InvalidParameterValueException(String.format("Unable to find IPv6 firewall rule with id %d", id)); + } + if (FirewallRule.TrafficType.Ingress.equals(rule.getTrafficType())) { + return firewallManager.revokeIngressFirewallRule(rule.getId(), true); + } + return firewallManager.revokeEgressFirewallRule(rule.getId(), true); + } + + @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_UPDATE, eventDescription = "updating IPv6 firewall rule", async = true) + public FirewallRule updateIpv6FirewallRule(UpdateIpv6FirewallRuleCmd cmd) { + final long id = cmd.getId(); + final boolean forDisplay = cmd.isDisplay(); + FirewallRuleVO rule = firewallDao.findById(id); + if (rule == null) { + throw new InvalidParameterValueException(String.format("Unable to find IPv6 firewall rule with id %d", id)); + } + if (FirewallRule.TrafficType.Ingress.equals(rule.getTrafficType())) { + return firewallManager.updateIngressFirewallRule(rule.getId(), null, forDisplay); + } + return firewallManager.updateEgressFirewallRule(rule.getId(), null, forDisplay); + } + + @Override + public FirewallRule getIpv6FirewallRule(Long entityId) { + return firewallDao.findById(entityId); + } + + @Override + public boolean applyIpv6FirewallRule(long id) { + FirewallRuleVO rule = firewallDao.findById(id); + if (rule == null) { + s_logger.error(String.format("Unable to find IPv6 firewall rule with ID: %d", id)); + return false; + } + if (!FirewallRule.Purpose.Ipv6Firewall.equals(rule.getPurpose())) { + s_logger.error(String.format("Cannot apply IPv6 firewall rule with ID: %d as purpose %s is not %s", id, rule.getPurpose(), FirewallRule.Purpose.Ipv6Firewall)); + } + s_logger.debug(String.format("Applying IPv6 firewall rules for rule with ID: %s", rule.getUuid())); + List rules = firewallDao.listByNetworkPurposeTrafficType(rule.getNetworkId(), rule.getPurpose(), FirewallRule.TrafficType.Egress); + rules.addAll(firewallDao.listByNetworkPurposeTrafficType(rule.getNetworkId(), FirewallRule.Purpose.Ipv6Firewall, FirewallRule.TrafficType.Ingress)); + return firewallManager.applyFirewallRules(rules, false, CallContext.current().getCallingAccount()); + } + + @Override + public void removePublicIpv6PlaceholderNics(Network network) { + try { + List nics = nicDao.listPlaceholderNicsByNetworkId(network.getId()) + .stream().filter(this::isPublicIpv6PlaceholderNic).collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(nics)) { + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + for (Nic nic : nics) { + s_logger.debug("Removing placeholder nic " + nic); + nicDao.remove(nic.getId()); + } + } + }); + } + } catch (Exception e) { + String msg = String.format("IPv6 Placeholder Nics trash. Exception: %s", e.getMessage()); + s_logger.error(msg); + throw new CloudRuntimeException(msg, e); + } + } + + public class Ipv6GuestPrefixSubnetNetworkMapStateScanner extends ManagedContextRunnable { + @Override + protected void runInContext() { + GlobalLock gcLock = GlobalLock.getInternLock("Ipv6GuestPrefixSubnetNetworkMap.State.Scanner.Lock"); + try { + if (gcLock.lock(3)) { + try { + reallyRun(); + } finally { + gcLock.unlock(); + } + } + } finally { + gcLock.releaseRef(); + } + } + + public void reallyRun() { + try { + List subnets = ipv6GuestPrefixSubnetNetworkMapDao.findPrefixesInStates(Ipv6GuestPrefixSubnetNetworkMap.State.Allocating); + for (Ipv6GuestPrefixSubnetNetworkMapVO subnet : subnets) { + if (s_logger.isInfoEnabled()) { + s_logger.info(String.format("Running state scanned on Ipv6GuestPrefixSubnetNetworkMap : %s", subnet.getSubnet())); + } + try { + if ((new Date()).getTime() - subnet.getUpdated().getTime() < Ipv6PrefixSubnetCleanupInterval.value()*1000) { + continue; + } + releaseIpv6Subnet(subnet.getId()); + } catch (CloudRuntimeException e) { + s_logger.warn(String.format("Failed to release IPv6 guest prefix subnet : %s during state scan", subnet.getSubnet()), e); + } + } + } catch (Exception e) { + s_logger.warn("Caught exception while running Ipv6GuestPrefixSubnetNetworkMap state scanner: ", e); + } + } + } +} diff --git a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java index ce66d97945d..748ea5c68a6 100644 --- a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java @@ -582,10 +582,9 @@ public class NetworkMigrationManagerImpl implements NetworkMigrationManager { //For each nic in the old network check if the nic belongs to a guest vm and migrate it to the new network. for (NicVO originalNic : nics) { - if (originalNic.getVmType() != VirtualMachine.Type.User) { + if (!VirtualMachine.Type.User.equals(originalNic.getVmType())) { continue; } - Transaction.execute((TransactionCallback) (status) -> migrateNicsInDB(originalNic, networkInNewPhysicalNet, dc, context)); } diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 8cb28664642..cb6e2a526d6 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -16,8 +16,6 @@ // under the License. package com.cloud.network; -import org.apache.commons.lang3.EnumUtils; - import java.net.Inet6Address; import java.net.InetAddress; import java.net.URI; @@ -75,6 +73,7 @@ import org.apache.cloudstack.network.NetworkPermissionVO; import org.apache.cloudstack.network.dao.NetworkPermissionDao; import org.apache.cloudstack.network.element.InternalLoadBalancerElementService; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; @@ -131,6 +130,7 @@ import com.cloud.network.dao.AccountGuestVlanMapVO; import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao; import com.cloud.network.dao.LoadBalancerDao; import com.cloud.network.dao.NetworkAccountDao; import com.cloud.network.dao.NetworkDao; @@ -227,6 +227,7 @@ import com.cloud.vm.dao.NicSecondaryIpDao; import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; +import com.googlecode.ipv6.IPv6Address; /** * NetworkServiceImpl implements NetworkService. @@ -359,6 +360,10 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C NetworkAccountDao _networkAccountDao; @Inject VirtualMachineManager vmManager; + @Inject + Ipv6Service ipv6Service; + @Inject + Ipv6GuestPrefixSubnetNetworkMapDao ipv6GuestPrefixSubnetNetworkMapDao; int _cidrLimit; boolean _allowSubdomainNetworkAccess; @@ -1472,6 +1477,15 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } validateRouterIps(routerIp, routerIpv6, startIP, endIP, gateway, netmask, startIPv6, endIPv6, ip6Cidr); + Pair ip6GatewayCidr = null; + if (zone.getNetworkType() == NetworkType.Advanced && ntwkOff.getGuestType() == GuestType.Isolated) { + ipv6 = _networkOfferingDao.isIpv6Supported(ntwkOff.getId()); + if (ipv6) { + ip6GatewayCidr = ipv6Service.preAllocateIpv6SubnetForNetwork(zone.getId()); + ip6Gateway = ip6GatewayCidr.first(); + ip6Cidr = ip6GatewayCidr.second(); + } + } if (StringUtils.isNotBlank(isolatedPvlan)) { if (!_accountMgr.isRootAdmin(caller.getId())) { @@ -1552,7 +1566,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C if (!createVlan) { // Only support advance shared network in IPv6, which means createVlan is a must - if (ipv6) { + if (ipv6 && ntwkOff.getGuestType() != GuestType.Isolated) { createVlan = true; } } @@ -1584,6 +1598,10 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C _networkDetailsDao.persist(new NetworkDetailVO(network.getId(), Network.hideIpAddressUsage, String.valueOf(hideIpAddressUsage), false)); } + if (ip6GatewayCidr != null) { + ipv6Service.assignIpv6SubnetToNetwork(ip6Cidr, network.getId()); + } + // if the network offering has persistent set to true, implement the network if (ntwkOff.isPersistent()) { return implementedNetworkInCreation(caller, zone, network); @@ -1775,7 +1793,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } } network = _vpcMgr.createVpcGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, networkDomain, owner, sharedDomainId, pNtwk, zoneId, aclType, - subdomainAccess, vpcId, aclId, caller, displayNetwork, externalId); + subdomainAccess, vpcId, aclId, caller, displayNetwork, externalId, ip6Gateway, ip6Cidr); } else { if (_configMgr.isOfferingForVpc(ntwkOff)) { throw new InvalidParameterValueException("Network offering can be used for VPC networks only"); @@ -2502,6 +2520,9 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C List nics = _nicDao.listByNetworkId(network.getId()); Network updatedNetwork = getNetwork(network.getId()); for (NicVO nic : nics) { + if (Nic.ReservationStrategy.PlaceHolder.equals(nic.getReservationStrategy())) { + continue; + } long vmId = nic.getInstanceId(); VMInstanceVO vm = _vmDao.findById(vmId); if (vm == null) { @@ -2658,6 +2679,15 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C if (!canUpgrade(network, oldNetworkOfferingId, networkOfferingId)) { throw new InvalidParameterValueException("Can't upgrade from network offering " + oldNtwkOff.getUuid() + " to " + networkOffering.getUuid() + "; check logs for more information"); } + boolean isIpv6Supported = _networkOfferingDao.isIpv6Supported(oldNetworkOfferingId); + boolean isIpv6SupportedNew = _networkOfferingDao.isIpv6Supported(networkOfferingId); + if (!isIpv6Supported && isIpv6SupportedNew) { + try { + ipv6Service.checkNetworkIpv6Upgrade(network); + } catch (ResourceAllocationException | InsufficientAddressCapacityException ex) { + throw new CloudRuntimeException(String.format("Failed to upgrade network offering to '%s' as unable to allocate IPv6 network", networkOffering.getDisplayText()), ex); + } + } restartNetwork = true; networkOfferingChanged = true; @@ -2670,298 +2700,346 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C ? _networkMgr.finalizeServicesAndProvidersForNetwork(_entityMgr.findById(NetworkOffering.class, networkOfferingId), network.getPhysicalNetworkId()) : new HashMap(); - // don't allow to modify network domain if the service is not supported - if (domainSuffix != null) { - // validate network domain - if (!NetUtils.verifyDomainName(domainSuffix)) { - throw new InvalidParameterValueException( - "Invalid network domain. Total length shouldn't exceed 190 chars. Each domain label must be between 1 and 63 characters long, can contain ASCII letters 'a' through 'z', the digits '0' through '9', " - + "and the hyphen ('-'); can't start or end with \"-\""); - } + // don't allow to modify network domain if the service is not supported + if (domainSuffix != null) { + // validate network domain + if (!NetUtils.verifyDomainName(domainSuffix)) { + throw new InvalidParameterValueException( + "Invalid network domain. Total length shouldn't exceed 190 chars. Each domain label must be between 1 and 63 characters long, can contain ASCII letters 'a' through 'z', the digits '0' through '9', " + + "and the hyphen ('-'); can't start or end with \"-\""); + } - long offeringId = oldNetworkOfferingId; - if (networkOfferingId != null) { - offeringId = networkOfferingId; - } + long offeringId = oldNetworkOfferingId; + if (networkOfferingId != null) { + offeringId = networkOfferingId; + } - Map dnsCapabilities = getNetworkOfferingServiceCapabilities(_entityMgr.findById(NetworkOffering.class, offeringId), Service.Dns); - String isUpdateDnsSupported = dnsCapabilities.get(Capability.AllowDnsSuffixModification); - if (isUpdateDnsSupported == null || !Boolean.valueOf(isUpdateDnsSupported)) { - // TBD: use uuid instead of networkOfferingId. May need to hardcode tablename in call to addProxyObject(). - throw new InvalidParameterValueException("Domain name change is not supported by the network offering id=" + networkOfferingId); - } + Map dnsCapabilities = getNetworkOfferingServiceCapabilities(_entityMgr.findById(NetworkOffering.class, offeringId), Service.Dns); + String isUpdateDnsSupported = dnsCapabilities.get(Capability.AllowDnsSuffixModification); + if (isUpdateDnsSupported == null || !Boolean.valueOf(isUpdateDnsSupported)) { + // TBD: use uuid instead of networkOfferingId. May need to hardcode tablename in call to addProxyObject(). + throw new InvalidParameterValueException("Domain name change is not supported by the network offering id=" + networkOfferingId); + } - network.setNetworkDomain(domainSuffix); - // have to restart the network - restartNetwork = true; + network.setNetworkDomain(domainSuffix); + // have to restart the network + restartNetwork = true; + } + + //IP reservation checks + // allow reservation only to Isolated Guest networks + DataCenter dc = _dcDao.findById(network.getDataCenterId()); + String networkCidr = network.getNetworkCidr(); + + if (guestVmCidr != null) { + if (dc.getNetworkType() == NetworkType.Basic) { + throw new InvalidParameterValueException("Guest VM CIDR can't be specified for zone with " + NetworkType.Basic + " networking"); + } + if (network.getGuestType() != GuestType.Isolated) { + throw new InvalidParameterValueException("Can only allow IP Reservation in networks with guest type " + GuestType.Isolated); + } + if (networkOfferingChanged) { + throw new InvalidParameterValueException("Cannot specify this nework offering change and guestVmCidr at same time. Specify only one."); + } + if (!(network.getState() == Network.State.Implemented)) { + throw new InvalidParameterValueException("The network must be in " + Network.State.Implemented + " state. IP Reservation cannot be applied in " + network.getState() + " state"); + } + if (!NetUtils.isValidIp4Cidr(guestVmCidr)) { + throw new InvalidParameterValueException("Invalid format of Guest VM CIDR."); + } + if (!NetUtils.validateGuestCidr(guestVmCidr)) { + throw new InvalidParameterValueException("Invalid format of Guest VM CIDR. Make sure it is RFC1918 compliant. "); + } + + // If networkCidr is null it implies that there was no prior IP reservation, so the network cidr is network.getCidr() + // But in case networkCidr is a non null value (IP reservation already exists), it implies network cidr is networkCidr + if (networkCidr != null) { + if (!NetUtils.isNetworkAWithinNetworkB(guestVmCidr, networkCidr)) { + throw new InvalidParameterValueException("Invalid value of Guest VM CIDR. For IP Reservation, Guest VM CIDR should be a subset of network CIDR : " + networkCidr); } - - //IP reservation checks - // allow reservation only to Isolated Guest networks - DataCenter dc = _dcDao.findById(network.getDataCenterId()); - String networkCidr = network.getNetworkCidr(); - - if (guestVmCidr != null) { - if (dc.getNetworkType() == NetworkType.Basic) { - throw new InvalidParameterValueException("Guest VM CIDR can't be specified for zone with " + NetworkType.Basic + " networking"); - } - if (network.getGuestType() != GuestType.Isolated) { - throw new InvalidParameterValueException("Can only allow IP Reservation in networks with guest type " + GuestType.Isolated); - } - if (networkOfferingChanged) { - throw new InvalidParameterValueException("Cannot specify this nework offering change and guestVmCidr at same time. Specify only one."); - } - if (!(network.getState() == Network.State.Implemented)) { - throw new InvalidParameterValueException("The network must be in " + Network.State.Implemented + " state. IP Reservation cannot be applied in " + network.getState() + " state"); - } - if (!NetUtils.isValidIp4Cidr(guestVmCidr)) { - throw new InvalidParameterValueException("Invalid format of Guest VM CIDR."); - } - if (!NetUtils.validateGuestCidr(guestVmCidr)) { - throw new InvalidParameterValueException("Invalid format of Guest VM CIDR. Make sure it is RFC1918 compliant. "); - } - - // If networkCidr is null it implies that there was no prior IP reservation, so the network cidr is network.getCidr() - // But in case networkCidr is a non null value (IP reservation already exists), it implies network cidr is networkCidr - if (networkCidr != null) { - if (!NetUtils.isNetworkAWithinNetworkB(guestVmCidr, networkCidr)) { - throw new InvalidParameterValueException("Invalid value of Guest VM CIDR. For IP Reservation, Guest VM CIDR should be a subset of network CIDR : " + networkCidr); - } - } else { - if (!NetUtils.isNetworkAWithinNetworkB(guestVmCidr, network.getCidr())) { - throw new InvalidParameterValueException("Invalid value of Guest VM CIDR. For IP Reservation, Guest VM CIDR should be a subset of network CIDR : " + network.getCidr()); - } - } - - // This check makes sure there are no active IPs existing outside the guestVmCidr in the network - String[] guestVmCidrPair = guestVmCidr.split("\\/"); - Long size = Long.valueOf(guestVmCidrPair[1]); - List nicsPresent = _nicDao.listByNetworkId(networkId); - - String cidrIpRange[] = NetUtils.getIpRangeFromCidr(guestVmCidrPair[0], size); - s_logger.info("The start IP of the specified guest vm cidr is: " + cidrIpRange[0] + " and end IP is: " + cidrIpRange[1]); - long startIp = NetUtils.ip2Long(cidrIpRange[0]); - long endIp = NetUtils.ip2Long(cidrIpRange[1]); - long range = endIp - startIp + 1; - s_logger.info("The specified guest vm cidr has " + range + " IPs"); - - for (NicVO nic : nicsPresent) { - long nicIp = NetUtils.ip2Long(nic.getIPv4Address()); - //check if nic IP is outside the guest vm cidr - if ((nicIp < startIp || nicIp > endIp) && nic.getState() != Nic.State.Deallocating) { - throw new InvalidParameterValueException("Active IPs like " + nic.getIPv4Address() + " exist outside the Guest VM CIDR. Cannot apply reservation "); - } - } - - // In some scenarios even though guesVmCidr and network CIDR do not appear similar but - // the IP ranges exactly matches, in these special cases make sure no Reservation gets applied - if (network.getNetworkCidr() == null) { - if (NetUtils.isSameIpRange(guestVmCidr, network.getCidr()) && !guestVmCidr.equals(network.getCidr())) { - throw new InvalidParameterValueException("The Start IP and End IP of guestvmcidr: " + guestVmCidr + " and CIDR: " + network.getCidr() + " are same, " - + "even though both the cidrs appear to be different. As a precaution no IP Reservation will be applied."); - } - } else { - if (NetUtils.isSameIpRange(guestVmCidr, network.getNetworkCidr()) && !guestVmCidr.equals(network.getNetworkCidr())) { - throw new InvalidParameterValueException("The Start IP and End IP of guestvmcidr: " + guestVmCidr + " and Network CIDR: " + network.getNetworkCidr() + " are same, " - + "even though both the cidrs appear to be different. As a precaution IP Reservation will not be affected. If you want to reset IP Reservation, " - + "specify guestVmCidr to be: " + network.getNetworkCidr()); - } - } - - // When reservation is applied for the first time, network_cidr will be null - // Populate it with the actual network cidr - if (network.getNetworkCidr() == null) { - network.setNetworkCidr(network.getCidr()); - } - - // Condition for IP Reservation reset : guestVmCidr and network CIDR are same - if (network.getNetworkCidr().equals(guestVmCidr)) { - s_logger.warn("Guest VM CIDR and Network CIDR both are same, reservation will reset."); - network.setNetworkCidr(null); - } - // Finally update "cidr" with the guestVmCidr - // which becomes the effective address space for CloudStack guest VMs - network.setCidr(guestVmCidr); - _networksDao.update(networkId, network); - s_logger.info("IP Reservation has been applied. The new CIDR for Guests Vms is " + guestVmCidr); + } else { + if (!NetUtils.isNetworkAWithinNetworkB(guestVmCidr, network.getCidr())) { + throw new InvalidParameterValueException("Invalid value of Guest VM CIDR. For IP Reservation, Guest VM CIDR should be a subset of network CIDR : " + network.getCidr()); } + } - ReservationContext context = new ReservationContextImpl(null, null, callerUser, callerAccount); - // 1) Shutdown all the elements and cleanup all the rules. Don't allow to shutdown network in intermediate - // states - Shutdown and Implementing - int resourceCount = 1; - if (updateInSequence && restartNetwork && _networkOfferingDao.findById(network.getNetworkOfferingId()).isRedundantRouter() - && (networkOfferingId == null || _networkOfferingDao.findById(networkOfferingId).isRedundantRouter()) && network.getVpcId() == null) { - _networkMgr.canUpdateInSequence(network, forced); - NetworkDetailVO networkDetail = new NetworkDetailVO(network.getId(), Network.updatingInSequence, "true", true); - _networkDetailsDao.persist(networkDetail); - _networkMgr.configureUpdateInSequence(network); - resourceCount = _networkMgr.getResourceCount(network); + // This check makes sure there are no active IPs existing outside the guestVmCidr in the network + String[] guestVmCidrPair = guestVmCidr.split("\\/"); + Long size = Long.valueOf(guestVmCidrPair[1]); + List nicsPresent = _nicDao.listByNetworkId(networkId); + + String cidrIpRange[] = NetUtils.getIpRangeFromCidr(guestVmCidrPair[0], size); + s_logger.info("The start IP of the specified guest vm cidr is: " + cidrIpRange[0] + " and end IP is: " + cidrIpRange[1]); + long startIp = NetUtils.ip2Long(cidrIpRange[0]); + long endIp = NetUtils.ip2Long(cidrIpRange[1]); + long range = endIp - startIp + 1; + s_logger.info("The specified guest vm cidr has " + range + " IPs"); + + for (NicVO nic : nicsPresent) { + if (nic.getIPv4Address() == null) { + continue; } - List servicesNotInNewOffering = null; - if (networkOfferingId != null) { - servicesNotInNewOffering = _networkMgr.getServicesNotSupportedInNewOffering(network, networkOfferingId); + long nicIp = NetUtils.ip2Long(nic.getIPv4Address()); + //check if nic IP is outside the guest vm cidr + if ((nicIp < startIp || nicIp > endIp) && nic.getState() != Nic.State.Deallocating) { + throw new InvalidParameterValueException("Active IPs like " + nic.getIPv4Address() + " exist outside the Guest VM CIDR. Cannot apply reservation "); } - if (!forced && servicesNotInNewOffering != null && !servicesNotInNewOffering.isEmpty()) { - NetworkOfferingVO newOffering = _networkOfferingDao.findById(networkOfferingId); - throw new CloudRuntimeException("The new offering:" + newOffering.getUniqueName() + " will remove the following services " + servicesNotInNewOffering - + "along with all the related configuration currently in use. will not proceed with the network update." + "set forced parameter to true for forcing an update."); + } + + // In some scenarios even though guesVmCidr and network CIDR do not appear similar but + // the IP ranges exactly matches, in these special cases make sure no Reservation gets applied + if (network.getNetworkCidr() == null) { + if (NetUtils.isSameIpRange(guestVmCidr, network.getCidr()) && !guestVmCidr.equals(network.getCidr())) { + throw new InvalidParameterValueException("The Start IP and End IP of guestvmcidr: " + guestVmCidr + " and CIDR: " + network.getCidr() + " are same, " + + "even though both the cidrs appear to be different. As a precaution no IP Reservation will be applied."); } - try { - if (servicesNotInNewOffering != null && !servicesNotInNewOffering.isEmpty()) { - _networkMgr.cleanupConfigForServicesInNetwork(servicesNotInNewOffering, network); - } - } catch (Throwable e) { - s_logger.debug("failed to cleanup config related to unused services error:" + e.getMessage()); + } else { + if (NetUtils.isSameIpRange(guestVmCidr, network.getNetworkCidr()) && !guestVmCidr.equals(network.getNetworkCidr())) { + throw new InvalidParameterValueException("The Start IP and End IP of guestvmcidr: " + guestVmCidr + " and Network CIDR: " + network.getNetworkCidr() + " are same, " + + "even though both the cidrs appear to be different. As a precaution IP Reservation will not be affected. If you want to reset IP Reservation, " + + "specify guestVmCidr to be: " + network.getNetworkCidr()); } + } - boolean validStateToShutdown = (network.getState() == Network.State.Implemented || network.getState() == Network.State.Setup || network.getState() == Network.State.Allocated); - try { + // When reservation is applied for the first time, network_cidr will be null + // Populate it with the actual network cidr + if (network.getNetworkCidr() == null) { + network.setNetworkCidr(network.getCidr()); + } - do { - if (restartNetwork) { - if (validStateToShutdown) { - if (!changeCidr) { - s_logger.debug("Shutting down elements and resources for network id=" + networkId + " as a part of network update"); + // Condition for IP Reservation reset : guestVmCidr and network CIDR are same + if (network.getNetworkCidr().equals(guestVmCidr)) { + s_logger.warn("Guest VM CIDR and Network CIDR both are same, reservation will reset."); + network.setNetworkCidr(null); + } + // Finally update "cidr" with the guestVmCidr + // which becomes the effective address space for CloudStack guest VMs + network.setCidr(guestVmCidr); + _networksDao.update(networkId, network); + s_logger.info("IP Reservation has been applied. The new CIDR for Guests Vms is " + guestVmCidr); + } - if (!_networkMgr.shutdownNetworkElementsAndResources(context, true, network)) { - s_logger.warn("Failed to shutdown the network elements and resources as a part of network restart: " + network); - CloudRuntimeException ex = new CloudRuntimeException("Failed to shutdown the network elements and resources as a part of update to network of specified id"); - ex.addProxyObject(network.getUuid(), "networkId"); - throw ex; - } - } else { - // We need to shutdown the network, since we want to re-implement the network. - s_logger.debug("Shutting down network id=" + networkId + " as a part of network update"); + ReservationContext context = new ReservationContextImpl(null, null, callerUser, callerAccount); + // 1) Shutdown all the elements and cleanup all the rules. Don't allow to shutdown network in intermediate + // states - Shutdown and Implementing + int resourceCount = 1; + if (updateInSequence && restartNetwork && _networkOfferingDao.findById(network.getNetworkOfferingId()).isRedundantRouter() + && (networkOfferingId == null || _networkOfferingDao.findById(networkOfferingId).isRedundantRouter()) && network.getVpcId() == null) { + _networkMgr.canUpdateInSequence(network, forced); + NetworkDetailVO networkDetail = new NetworkDetailVO(network.getId(), Network.updatingInSequence, "true", true); + _networkDetailsDao.persist(networkDetail); + _networkMgr.configureUpdateInSequence(network); + resourceCount = _networkMgr.getResourceCount(network); + } + List servicesNotInNewOffering = null; + if (networkOfferingId != null) { + servicesNotInNewOffering = _networkMgr.getServicesNotSupportedInNewOffering(network, networkOfferingId); + } + if (!forced && servicesNotInNewOffering != null && !servicesNotInNewOffering.isEmpty()) { + NetworkOfferingVO newOffering = _networkOfferingDao.findById(networkOfferingId); + throw new CloudRuntimeException("The new offering:" + newOffering.getUniqueName() + " will remove the following services " + servicesNotInNewOffering + + "along with all the related configuration currently in use. will not proceed with the network update." + "set forced parameter to true for forcing an update."); + } + try { + if (servicesNotInNewOffering != null && !servicesNotInNewOffering.isEmpty()) { + _networkMgr.cleanupConfigForServicesInNetwork(servicesNotInNewOffering, network); + } + } catch (Throwable e) { + s_logger.debug("failed to cleanup config related to unused services error:" + e.getMessage()); + } - //check if network has reservation - if (NetUtils.isNetworkAWithinNetworkB(network.getCidr(), network.getNetworkCidr())) { - s_logger.warn( - "Existing IP reservation will become ineffective for the network with id = " + networkId + " You need to reapply reservation after network reimplementation."); - //set cidr to the newtork cidr - network.setCidr(network.getNetworkCidr()); - //set networkCidr to null to bring network back to no IP reservation state - network.setNetworkCidr(null); - } + boolean validStateToShutdown = (network.getState() == Network.State.Implemented || network.getState() == Network.State.Setup || network.getState() == Network.State.Allocated); + try { - if (!_networkMgr.shutdownNetwork(network.getId(), context, true)) { - s_logger.warn("Failed to shutdown the network as a part of update to network with specified id"); - CloudRuntimeException ex = new CloudRuntimeException("Failed to shutdown the network as a part of update of specified network id"); - ex.addProxyObject(network.getUuid(), "networkId"); - throw ex; - } - } - } else { - CloudRuntimeException ex = new CloudRuntimeException( - "Failed to shutdown the network elements and resources as a part of update to network with specified id; network is in wrong state: " + network.getState()); + do { + if (restartNetwork) { + if (validStateToShutdown) { + if (!changeCidr) { + s_logger.debug("Shutting down elements and resources for network id=" + networkId + " as a part of network update"); + + if (!_networkMgr.shutdownNetworkElementsAndResources(context, true, network)) { + s_logger.warn("Failed to shutdown the network elements and resources as a part of network restart: " + network); + CloudRuntimeException ex = new CloudRuntimeException("Failed to shutdown the network elements and resources as a part of update to network of specified id"); + ex.addProxyObject(network.getUuid(), "networkId"); + throw ex; + } + } else { + // We need to shutdown the network, since we want to re-implement the network. + s_logger.debug("Shutting down network id=" + networkId + " as a part of network update"); + + //check if network has reservation + if (NetUtils.isNetworkAWithinNetworkB(network.getCidr(), network.getNetworkCidr())) { + s_logger.warn( + "Existing IP reservation will become ineffective for the network with id = " + networkId + " You need to reapply reservation after network reimplementation."); + //set cidr to the newtork cidr + network.setCidr(network.getNetworkCidr()); + //set networkCidr to null to bring network back to no IP reservation state + network.setNetworkCidr(null); + } + + if (!_networkMgr.shutdownNetwork(network.getId(), context, true)) { + s_logger.warn("Failed to shutdown the network as a part of update to network with specified id"); + CloudRuntimeException ex = new CloudRuntimeException("Failed to shutdown the network as a part of update of specified network id"); ex.addProxyObject(network.getUuid(), "networkId"); throw ex; } } - - // 2) Only after all the elements and rules are shutdown properly, update the network VO - // get updated network - Network.State networkState = _networksDao.findById(networkId).getState(); - boolean validStateToImplement = (networkState == Network.State.Implemented || networkState == Network.State.Setup || networkState == Network.State.Allocated); - if (restartNetwork && !validStateToImplement) { - CloudRuntimeException ex = new CloudRuntimeException( - "Failed to implement the network elements and resources as a part of update to network with specified id; network is in wrong state: " + networkState); - ex.addProxyObject(network.getUuid(), "networkId"); - throw ex; - } - - if (networkOfferingId != null) { - if (networkOfferingChanged) { - Transaction.execute(new TransactionCallbackNoReturn() { - @Override - public void doInTransactionWithoutResult(TransactionStatus status) { - network.setNetworkOfferingId(networkOfferingId); - _networksDao.update(networkId, network, newSvcProviders); - // get all nics using this network - // log remove usage events for old offering - // log assign usage events for new offering - List nics = _nicDao.listByNetworkId(networkId); - for (NicVO nic : nics) { - if (nic.getReservationStrategy() == Nic.ReservationStrategy.PlaceHolder) { - continue; - } - long vmId = nic.getInstanceId(); - VMInstanceVO vm = _vmDao.findById(vmId); - if (vm == null) { - s_logger.error("Vm for nic " + nic.getId() + " not found with Vm Id:" + vmId); - continue; - } - long isDefault = (nic.isDefaultNic()) ? 1 : 0; - String nicIdString = Long.toString(nic.getId()); - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), nicIdString, oldNetworkOfferingId, - null, isDefault, VirtualMachine.class.getName(), vm.getUuid(), vm.isDisplay()); - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_ASSIGN, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), nicIdString, networkOfferingId, - null, isDefault, VirtualMachine.class.getName(), vm.getUuid(), vm.isDisplay()); - } - } - }); - } else { - network.setNetworkOfferingId(networkOfferingId); - _networksDao.update(networkId, network, - _networkMgr.finalizeServicesAndProvidersForNetwork(_entityMgr.findById(NetworkOffering.class, networkOfferingId), network.getPhysicalNetworkId())); - } - } else { - _networksDao.update(networkId, network); - } - - // 3) Implement the elements and rules again - if (restartNetwork) { - if (network.getState() != Network.State.Allocated) { - DeployDestination dest = new DeployDestination(_dcDao.findById(network.getDataCenterId()), null, null, null); - s_logger.debug("Implementing the network " + network + " elements and resources as a part of network update"); - try { - if (!changeCidr) { - _networkMgr.implementNetworkElementsAndResources(dest, context, network, _networkOfferingDao.findById(network.getNetworkOfferingId())); - } else { - _networkMgr.implementNetwork(network.getId(), dest, context); - } - } catch (Exception ex) { - s_logger.warn("Failed to implement network " + network + " elements and resources as a part of network update due to ", ex); - CloudRuntimeException e = new CloudRuntimeException("Failed to implement network (with specified id) elements and resources as a part of network update"); - e.addProxyObject(network.getUuid(), "networkId"); - throw e; - } - } - if (networkOfferingChanged) { - replugNicsForUpdatedNetwork(network); - } - } - - // 4) if network has been upgraded from a non persistent ntwk offering to a persistent ntwk offering, - // implement the network if its not already - if (networkOfferingChanged && !oldNtwkOff.isPersistent() && networkOffering.isPersistent()) { - if (network.getState() == Network.State.Allocated) { - try { - DeployDestination dest = new DeployDestination(_dcDao.findById(network.getDataCenterId()), null, null, null); - _networkMgr.implementNetwork(network.getId(), dest, context); - } catch (Exception ex) { - s_logger.warn("Failed to implement network " + network + " elements and resources as a part o" + "f network update due to ", ex); - CloudRuntimeException e = new CloudRuntimeException("Failed to implement network (with specified" + " id) elements and resources as a part of network update"); - e.addProxyObject(network.getUuid(), "networkId"); - throw e; - } - } - } - resourceCount--; - } while (updateInSequence && resourceCount > 0); - } catch (Exception exception) { - if (updateInSequence) { - _networkMgr.finalizeUpdateInSequence(network, false); + } else { + CloudRuntimeException ex = new CloudRuntimeException( + "Failed to shutdown the network elements and resources as a part of update to network with specified id; network is in wrong state: " + network.getState()); + ex.addProxyObject(network.getUuid(), "networkId"); + throw ex; } - throw new CloudRuntimeException("failed to update network " + network.getUuid() + " due to " + exception.getMessage()); - } finally { - if (updateInSequence) { - if (_networkDetailsDao.findDetail(networkId, Network.updatingInSequence) != null) { - _networkDetailsDao.removeDetail(networkId, Network.updatingInSequence); + } + + // 2) Only after all the elements and rules are shutdown properly, update the network VO + // get updated network + Network.State networkState = _networksDao.findById(networkId).getState(); + boolean validStateToImplement = (networkState == Network.State.Implemented || networkState == Network.State.Setup || networkState == Network.State.Allocated); + if (restartNetwork && !validStateToImplement) { + CloudRuntimeException ex = new CloudRuntimeException( + "Failed to implement the network elements and resources as a part of update to network with specified id; network is in wrong state: " + networkState); + ex.addProxyObject(network.getUuid(), "networkId"); + throw ex; + } + + if (networkOfferingId != null) { + if (networkOfferingChanged) { + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + updateNetworkIpv6(network, networkOfferingId); + network.setNetworkOfferingId(networkOfferingId); + _networksDao.update(networkId, network, newSvcProviders); + // get all nics using this network + // log remove usage events for old offering + // log assign usage events for new offering + List nics = _nicDao.listByNetworkId(networkId); + for (NicVO nic : nics) { + if (Nic.ReservationStrategy.PlaceHolder.equals(nic.getReservationStrategy())) { + continue; + } + long vmId = nic.getInstanceId(); + VMInstanceVO vm = _vmDao.findById(vmId); + if (vm == null) { + s_logger.error("Vm for nic " + nic.getId() + " not found with Vm Id:" + vmId); + continue; + } + long isDefault = (nic.isDefaultNic()) ? 1 : 0; + String nicIdString = Long.toString(nic.getId()); + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), nicIdString, oldNetworkOfferingId, + null, isDefault, VirtualMachine.class.getName(), vm.getUuid(), vm.isDisplay()); + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_ASSIGN, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), nicIdString, networkOfferingId, + null, isDefault, VirtualMachine.class.getName(), vm.getUuid(), vm.isDisplay()); + } + } + }); + } else { + network.setNetworkOfferingId(networkOfferingId); + _networksDao.update(networkId, network, + _networkMgr.finalizeServicesAndProvidersForNetwork(_entityMgr.findById(NetworkOffering.class, networkOfferingId), network.getPhysicalNetworkId())); + } + } else { + _networksDao.update(networkId, network); + } + + // 3) Implement the elements and rules again + if (restartNetwork) { + if (network.getState() != Network.State.Allocated) { + DeployDestination dest = new DeployDestination(_dcDao.findById(network.getDataCenterId()), null, null, null); + s_logger.debug("Implementing the network " + network + " elements and resources as a part of network update"); + try { + if (!changeCidr) { + _networkMgr.implementNetworkElementsAndResources(dest, context, network, _networkOfferingDao.findById(network.getNetworkOfferingId())); + } else { + _networkMgr.implementNetwork(network.getId(), dest, context); + } + } catch (Exception ex) { + s_logger.warn("Failed to implement network " + network + " elements and resources as a part of network update due to ", ex); + CloudRuntimeException e = new CloudRuntimeException("Failed to implement network (with specified id) elements and resources as a part of network update"); + e.addProxyObject(network.getUuid(), "networkId"); + throw e; + } + } + if (networkOfferingChanged) { + replugNicsForUpdatedNetwork(network); + } + } + + // 4) if network has been upgraded from a non persistent ntwk offering to a persistent ntwk offering, + // implement the network if its not already + if (networkOfferingChanged && !oldNtwkOff.isPersistent() && networkOffering.isPersistent()) { + if (network.getState() == Network.State.Allocated) { + try { + DeployDestination dest = new DeployDestination(_dcDao.findById(network.getDataCenterId()), null, null, null); + _networkMgr.implementNetwork(network.getId(), dest, context); + } catch (Exception ex) { + s_logger.warn("Failed to implement network " + network + " elements and resources as a part o" + "f network update due to ", ex); + CloudRuntimeException e = new CloudRuntimeException("Failed to implement network (with specified" + " id) elements and resources as a part of network update"); + e.addProxyObject(network.getUuid(), "networkId"); + throw e; } } } - return getNetwork(network.getId()); + resourceCount--; + } while (updateInSequence && resourceCount > 0); + } catch (Exception exception) { + if (updateInSequence) { + _networkMgr.finalizeUpdateInSequence(network, false); + } + throw new CloudRuntimeException("failed to update network " + network.getUuid() + " due to " + exception.getMessage(), exception); + } finally { + if (updateInSequence) { + if (_networkDetailsDao.findDetail(networkId, Network.updatingInSequence) != null) { + _networkDetailsDao.removeDetail(networkId, Network.updatingInSequence); + } + } + } + return getNetwork(network.getId()); + } + + private void updateNetworkIpv6(NetworkVO network, Long networkOfferingId) { + boolean isIpv6Supported = _networkOfferingDao.isIpv6Supported(network.getNetworkOfferingId()); + boolean isIpv6SupportedNew = _networkOfferingDao.isIpv6Supported(networkOfferingId); + if (isIpv6Supported && ! isIpv6SupportedNew) { +// _ipv6AddressDao.unmark(network.getId(), network.getDomainId(), network.getAccountId()); + network.setIp6Gateway(null); + network.setIp6Cidr(null); + List nics = _nicDao.listByNetworkId(network.getId()); + for (NicVO nic : nics) { + if (Nic.ReservationStrategy.PlaceHolder.equals(nic.getReservationStrategy())) { + continue; + } + nic.setIPv6Address(null); + nic.setIPv6Cidr(null); + nic.setIPv6Gateway(null); + _nicDao.update(nic.getId(), nic); + } + } else if (!isIpv6Supported && isIpv6SupportedNew) { + Pair ip6GatewayCidr; + try { + ip6GatewayCidr = ipv6Service.preAllocateIpv6SubnetForNetwork(network.getDataCenterId()); + ipv6Service.assignIpv6SubnetToNetwork(ip6GatewayCidr.second(), network.getId()); + } catch (ResourceAllocationException ex) { + throw new CloudRuntimeException("unable to allocate IPv6 network", ex); + } + String ip6Gateway = ip6GatewayCidr.first(); + String ip6Cidr = ip6GatewayCidr.second(); + network.setIp6Gateway(ip6Gateway); + network.setIp6Cidr(ip6Cidr); + Ipv6GuestPrefixSubnetNetworkMapVO map = ipv6GuestPrefixSubnetNetworkMapDao.findByNetworkId(network.getId()); + List nics = _nicDao.listByNetworkId(network.getId()); + for (NicVO nic : nics) { + if (Nic.ReservationStrategy.PlaceHolder.equals(nic.getReservationStrategy())) { + continue; + } + IPv6Address iPv6Address = NetUtils.EUI64Address(map.getSubnet(), nic.getMacAddress()); + nic.setIPv6Address(iPv6Address.toString()); + nic.setIPv6Cidr(ip6Cidr); + nic.setIPv6Gateway(ip6Gateway); + _nicDao.update(nic.getId(), nic); + } + } } @Override @@ -5308,5 +5386,4 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C public ConfigKey[] getConfigKeys() { return new ConfigKey[] {AllowDuplicateNetworkName, AllowEmptyStartEndIpAddress}; } - } diff --git a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java index 0d13712679b..b08df5a3d1b 100644 --- a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java +++ b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java @@ -17,6 +17,7 @@ package com.cloud.network.firewall; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -28,6 +29,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.api.command.user.firewall.IListFirewallRulesCmd; +import org.apache.cloudstack.api.command.user.ipv6.ListIpv6FirewallRulesCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -337,12 +339,19 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, sc.setParameters("ip", ipId); } - if (networkId != null) { - sc.setParameters("networkId", networkId); - } + if (networkId != null) { + sc.setParameters("networkId", networkId); + } - sc.setParameters("purpose", Purpose.Firewall); - sc.setParameters("trafficType", trafficType); + if (cmd instanceof ListIpv6FirewallRulesCmd) { + sc.setParameters("purpose", Purpose.Ipv6Firewall); + } else { + sc.setParameters("purpose", Purpose.Firewall); + } + + if (trafficType != null) { + sc.setParameters("trafficType", trafficType); + } Pair, Integer> result = _firewallDao.searchAndCount(sc, filter); return new Pair, Integer>(result.first(), result.second()); @@ -555,7 +564,14 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, return true; } Purpose purpose = rules.get(0).getPurpose(); - if (!_ipAddrMgr.applyRules(rules, purpose, this, continueOnError)) { + boolean applied; + if (purpose.equals(Purpose.Ipv6Firewall)) { + Network network = _networkDao.findById(rules.get(0).getNetworkId()); + applied = applyRules(network, purpose, rules); + } else { + applied = _ipAddrMgr.applyRules(rules, purpose, this, continueOnError); + } + if (!applied) { s_logger.warn("Rules are not completely applied"); return false; } else { @@ -594,7 +610,8 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, /* StaticNatRule would be applied by Firewall provider, since the incompatible of two object */ case StaticNat: case Firewall: - for (FirewallServiceProvider fwElement : _firewallElements) { + case Ipv6Firewall: + for (FirewallServiceProvider fwElement : _firewallElements) { Network.Provider provider = fwElement.getProvider(); boolean isFwProvider = _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Firewall, provider); if (!isFwProvider) { @@ -659,8 +676,8 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, @Override @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_EGRESS_OPEN, eventDescription = "creating egress firewall rule", async = true) public boolean applyEgressFirewallRules(FirewallRule rule, Account caller) throws ResourceUnavailableException { - List rules = _firewallDao.listByNetworkPurposeTrafficType(rule.getNetworkId(), Purpose.Firewall, FirewallRule.TrafficType.Egress); - return applyFirewallRules(rules, false, caller); + List rules = _firewallDao.listByNetworkPurposeTrafficType(rule.getNetworkId(), Purpose.Firewall, FirewallRule.TrafficType.Egress); + return applyFirewallRules(rules, false, caller); } @Override @@ -726,10 +743,9 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, } protected boolean revokeFirewallRule(long ruleId, boolean apply, Account caller, long userId) { - FirewallRuleVO rule = _firewallDao.findById(ruleId); - if (rule == null || rule.getPurpose() != Purpose.Firewall) { - throw new InvalidParameterValueException("Unable to find " + ruleId + " having purpose " + Purpose.Firewall); + if (rule == null || !Arrays.asList(Purpose.Firewall, Purpose.Ipv6Firewall).contains(rule.getPurpose())) { + throw new InvalidParameterValueException("Unable to find " + ruleId + " having purpose " + Arrays.asList(Purpose.Firewall, Purpose.Ipv6Firewall)); } if (rule.getType() == FirewallRuleType.System && !_accountMgr.isRootAdmin(caller.getId())) { @@ -747,11 +763,15 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService, // ingress firewall rule if (rule.getSourceIpAddressId() != null) { //feteches ingress firewall, ingress firewall rules associated with the ip - List rules = _firewallDao.listByIpAndPurpose(rule.getSourceIpAddressId(), Purpose.Firewall); - return applyFirewallRules(rules, false, caller); + List rules = _firewallDao.listByIpAndPurpose(rule.getSourceIpAddressId(), Purpose.Firewall); + return applyFirewallRules(rules, false, caller); //egress firewall rule } else if (networkId != null) { - List rules = _firewallDao.listByNetworkPurposeTrafficType(rule.getNetworkId(), Purpose.Firewall, FirewallRule.TrafficType.Egress); + boolean isIpv6 = Purpose.Ipv6Firewall.equals(rule.getPurpose()); + List rules = _firewallDao.listByNetworkPurposeTrafficType(rule.getNetworkId(), rule.getPurpose(), FirewallRule.TrafficType.Egress); + if (isIpv6) { + rules.addAll(_firewallDao.listByNetworkPurposeTrafficType(rule.getNetworkId(), Purpose.Ipv6Firewall, FirewallRule.TrafficType.Ingress)); + } return applyFirewallRules(rules, false, caller); } } else { diff --git a/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java index 09824a07355..6524abe1e45 100644 --- a/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java +++ b/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java @@ -36,6 +36,7 @@ import com.cloud.event.EventTypes; import com.cloud.event.EventVO; import com.cloud.exception.InsufficientAddressCapacityException; import com.cloud.exception.InsufficientVirtualNetworkCapacityException; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.network.IpAddressManager; import com.cloud.network.Network; import com.cloud.network.Network.GuestType; @@ -122,6 +123,20 @@ public class ExternalGuestNetworkGuru extends GuestNetworkGuru { /* In order to revert userSpecified network setup */ config.setState(State.Allocated); } + if (userSpecified == null) { + return config; + } + if ((userSpecified.getIp6Cidr() == null && userSpecified.getIp6Gateway() != null) || + (userSpecified.getIp6Cidr() != null && userSpecified.getIp6Gateway() == null)) { + throw new InvalidParameterValueException("ip6gateway and ip6cidr must be specified together."); + } + if (userSpecified.getIp6Cidr() != null) { + config.setIp6Cidr(userSpecified.getIp6Cidr()); + config.setIp6Gateway(userSpecified.getIp6Gateway()); + } + if (userSpecified.getRouterIpv6() != null) { + config.setRouterIpv6(userSpecified.getRouterIpv6()); + } return config; } diff --git a/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java index 0ff82f54732..be3d57eb48d 100644 --- a/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java +++ b/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java @@ -44,6 +44,7 @@ import com.cloud.exception.InsufficientAddressCapacityException; import com.cloud.exception.InsufficientVirtualNetworkCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.network.IpAddressManager; +import com.cloud.network.Ipv6AddressManager; import com.cloud.network.Network; import com.cloud.network.Network.GuestType; import com.cloud.network.Network.Provider; @@ -51,6 +52,7 @@ import com.cloud.network.Network.Service; import com.cloud.network.Network.State; import com.cloud.network.NetworkModel; import com.cloud.network.NetworkProfile; +import com.cloud.network.Networks; import com.cloud.network.Networks.AddressFormat; import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.Networks.Mode; @@ -66,6 +68,7 @@ import com.cloud.network.dao.PhysicalNetworkVO; import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.server.ConfigurationServer; import com.cloud.user.Account; import com.cloud.utils.Pair; @@ -76,12 +79,14 @@ import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; +import com.cloud.vm.DomainRouterVO; import com.cloud.vm.Nic; import com.cloud.vm.Nic.ReservationStrategy; import com.cloud.vm.NicProfile; import com.cloud.vm.ReservationContext; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGuru, Configurable { @@ -111,6 +116,13 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur ConfigurationServer _configServer; @Inject IpAddressManager _ipAddrMgr; + @Inject + NetworkOfferingDao networkOfferingDao; + @Inject + Ipv6AddressManager ipv6AddressManager; + @Inject + DomainRouterDao domainRouterDao; + Random _rand = new Random(System.currentTimeMillis()); public static final ConfigKey UseSystemGuestVlans = @@ -135,6 +147,29 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur _isolationMethods = null; } + private void updateNicIpv6(Network network, NicProfile nic, VirtualMachineProfile vm, DataCenter dc, boolean isGateway) throws InsufficientAddressCapacityException { + boolean isIpv6Supported = networkOfferingDao.isIpv6Supported(network.getNetworkOfferingId()); + if (!isIpv6Supported || nic.getIPv6Address() != null || network.getIp6Cidr() == null || network.getIp6Gateway() == null) { + return; + } + if (isGateway) { + nic.setIPv6Cidr(network.getIp6Cidr()); + nic.setIPv6Gateway(network.getIp6Gateway()); + if (nic.getIPv4Address() != null) { + nic.setFormat(Networks.AddressFormat.DualStack); + } else { + nic.setFormat(Networks.AddressFormat.Ip6); + } + DomainRouterVO router = domainRouterDao.findById(vm.getId()); + if (router != null && + router.getIsRedundantRouter()) { + return; + } + nic.setIPv6Address(network.getIp6Gateway()); + } + ipv6AddressManager.setNicIp6Address(nic, dc, network); + } + @Override public boolean isMyTrafficType(final TrafficType type) { for (final TrafficType t : TrafficTypes) { @@ -349,6 +384,22 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur final DataCenter dc = _dcDao.findById(network.getDataCenterId()); + boolean isGateway = false; + //if Vm is router vm and source nat is enabled in the network, set ip4 to the network gateway + if (vm.getVirtualMachine().getType() == VirtualMachine.Type.DomainRouter) { + if (network.getVpcId() != null) { + final Vpc vpc = _vpcDao.findById(network.getVpcId()); + // Redundant Networks need a guest IP that is not the same as the gateway IP. + if (_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.SourceNat, Provider.VPCVirtualRouter) && !vpc.isRedundant()) { + isGateway = true; + } + } else { + if (_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.SourceNat, Provider.VirtualRouter)) { + isGateway = true; + } + } + } + if (nic.getIPv4Address() == null) { nic.setBroadcastUri(network.getBroadcastUri()); nic.setIsolationUri(network.getBroadcastUri()); @@ -358,22 +409,6 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur if (network.getSpecifyIpRanges()) { _ipAddrMgr.allocateDirectIp(nic, dc, vm, network, nic.getRequestedIPv4(), null); } else { - //if Vm is router vm and source nat is enabled in the network, set ip4 to the network gateway - boolean isGateway = false; - if (vm.getVirtualMachine().getType() == VirtualMachine.Type.DomainRouter) { - if (network.getVpcId() != null) { - final Vpc vpc = _vpcDao.findById(network.getVpcId()); - // Redundant Networks need a guest IP that is not the same as the gateway IP. - if (_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.SourceNat, Provider.VPCVirtualRouter) && !vpc.isRedundant()) { - isGateway = true; - } - } else { - if (_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.SourceNat, Provider.VirtualRouter)) { - isGateway = true; - } - } - } - if (isGateway) { guestIp = network.getGateway(); } else { @@ -416,7 +451,7 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur throw new InsufficientAddressCapacityException("Unable to allocate more mac addresses", Network.class, network.getId()); } } - + updateNicIpv6(network, nic, vm, dc, isGateway); return nic; } diff --git a/server/src/main/java/com/cloud/network/guru/PublicNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/PublicNetworkGuru.java index 330a3652b2f..c3f7ee3d941 100644 --- a/server/src/main/java/com/cloud/network/guru/PublicNetworkGuru.java +++ b/server/src/main/java/com/cloud/network/guru/PublicNetworkGuru.java @@ -31,6 +31,7 @@ import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientAddressCapacityException; import com.cloud.exception.InsufficientVirtualNetworkCapacityException; import com.cloud.network.IpAddressManager; +import com.cloud.network.Ipv6Service; import com.cloud.network.Network; import com.cloud.network.Network.State; import com.cloud.network.NetworkProfile; @@ -70,6 +71,8 @@ public class PublicNetworkGuru extends AdapterBase implements NetworkGuru { IPAddressDao _ipAddressDao; @Inject IpAddressManager _ipAddrMgr; + @Inject + Ipv6Service ipv6Service; private static final TrafficType[] TrafficTypes = {TrafficType.Public}; @@ -139,6 +142,8 @@ public class PublicNetworkGuru extends AdapterBase implements NetworkGuru { nic.setIPv4Dns1(dc.getDns1()); nic.setIPv4Dns2(dc.getDns2()); + + ipv6Service.updateNicIpv6(nic, dc, network); } @Override diff --git a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java index 9ab633da779..5cf9f51a7d6 100644 --- a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java +++ b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java @@ -26,12 +26,6 @@ import java.util.Map; import javax.inject.Inject; -import com.cloud.configuration.ConfigurationManager; -import com.cloud.hypervisor.Hypervisor; -import com.cloud.network.dao.NetworkDetailVO; -import com.cloud.network.dao.NetworkDetailsDao; -import com.cloud.offerings.dao.NetworkOfferingDetailsDao; -import com.cloud.vm.VmDetailConstants; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -52,6 +46,7 @@ import com.cloud.agent.api.routing.NetworkElementCommand; import com.cloud.agent.api.routing.RemoteAccessVpnCfgCommand; import com.cloud.agent.api.routing.SavePasswordCommand; import com.cloud.agent.api.routing.SetFirewallRulesCommand; +import com.cloud.agent.api.routing.SetIpv6FirewallRulesCommand; import com.cloud.agent.api.routing.SetNetworkACLCommand; import com.cloud.agent.api.routing.SetPortForwardingRulesCommand; import com.cloud.agent.api.routing.SetPortForwardingRulesVpcCommand; @@ -71,6 +66,7 @@ import com.cloud.agent.api.to.PortForwardingRuleTO; import com.cloud.agent.api.to.StaticNatRuleTO; import com.cloud.agent.manager.Commands; import com.cloud.configuration.Config; +import com.cloud.configuration.ConfigurationManager; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter.NetworkType; import com.cloud.dc.DataCenterVO; @@ -78,7 +74,9 @@ import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.VlanDao; import com.cloud.host.Host; import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor; import com.cloud.network.IpAddress; +import com.cloud.network.Ipv6Service; import com.cloud.network.Network; import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; @@ -92,9 +90,11 @@ import com.cloud.network.VpnUser; import com.cloud.network.VpnUserVO; import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; -import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkVO; import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkDetailVO; +import com.cloud.network.dao.NetworkDetailsDao; +import com.cloud.network.dao.NetworkVO; import com.cloud.network.dao.Site2SiteCustomerGatewayDao; import com.cloud.network.dao.Site2SiteCustomerGatewayVO; import com.cloud.network.dao.Site2SiteVpnGatewayDao; @@ -118,6 +118,7 @@ import com.cloud.network.vpc.dao.VpcDao; import com.cloud.offering.NetworkOffering; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.offerings.dao.NetworkOfferingDetailsDao; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.user.Account; import com.cloud.uservm.UserVm; @@ -134,6 +135,7 @@ import com.cloud.vm.UserVmVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.NicIpAliasDao; @@ -193,6 +195,8 @@ public class CommandSetupHelper { private NetworkOfferingDetailsDao networkOfferingDetailsDao; @Inject private NetworkDetailsDao networkDetailsDao; + @Inject + Ipv6Service ipv6Service; @Autowired @Qualifier("networkHelper") @@ -454,6 +458,48 @@ public class CommandSetupHelper { cmds.addCommand(cmd); } + public void createApplyIpv6FirewallRulesCommands(final List rules, final VirtualRouter router, final Commands cmds, final long guestNetworkId) { + final List rulesTO = new ArrayList<>(); + String systemRule = null; + Boolean defaultEgressPolicy = false; + if (rules != null) { + if (rules.size() > 0) { + if (rules.get(0).getTrafficType() == FirewallRule.TrafficType.Egress && rules.get(0).getType() == FirewallRule.FirewallRuleType.System) { + systemRule = String.valueOf(FirewallRule.FirewallRuleType.System); + } + } + for (final FirewallRule rule : rules) { + _rulesDao.loadSourceCidrs((FirewallRuleVO)rule); + _rulesDao.loadDestinationCidrs((FirewallRuleVO)rule); + final FirewallRule.TrafficType trafficType = rule.getTrafficType(); + if (trafficType == FirewallRule.TrafficType.Ingress) { + final FirewallRuleTO ruleTO = new FirewallRuleTO(rule, null, null, Purpose.Ipv6Firewall, trafficType); + rulesTO.add(ruleTO); + } else if (rule.getTrafficType() == FirewallRule.TrafficType.Egress) { + final NetworkVO network = _networkDao.findById(guestNetworkId); + final NetworkOfferingVO offering = _networkOfferingDao.findById(network.getNetworkOfferingId()); + defaultEgressPolicy = offering.isEgressDefaultPolicy(); + final FirewallRuleTO ruleTO = new FirewallRuleTO(rule, null, "", Purpose.Ipv6Firewall, trafficType, defaultEgressPolicy); + rulesTO.add(ruleTO); + } + } + } + + final SetIpv6FirewallRulesCommand cmd = new SetIpv6FirewallRulesCommand(rulesTO); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, _routerControlHelper.getRouterControlIp(router.getId())); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_GUEST_IP, _routerControlHelper.getRouterIpInNetwork(guestNetworkId, router.getId())); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, router.getInstanceName()); + final DataCenterVO dcVo = _dcDao.findById(router.getDataCenterId()); + cmd.setAccessDetail(NetworkElementCommand.ZONE_NETWORK_TYPE, dcVo.getNetworkType().toString()); + if (systemRule != null) { + cmd.setAccessDetail(NetworkElementCommand.FIREWALL_EGRESS_DEFAULT, systemRule); + } else { + cmd.setAccessDetail(NetworkElementCommand.FIREWALL_EGRESS_DEFAULT, String.valueOf(defaultEgressPolicy)); + } + + cmds.addCommand(cmd); + } + public void createFirewallRulesCommands(final List rules, final VirtualRouter router, final Commands cmds, final long guestNetworkId) { final List rulesTO = new ArrayList(); String systemRule = null; @@ -498,6 +544,48 @@ public class CommandSetupHelper { cmds.addCommand(cmd); } + public void createIpv6FirewallRulesCommands(final List rules, final VirtualRouter router, final Commands cmds, final long guestNetworkId) { + final List rulesTO = new ArrayList<>(); + String systemRule = null; + Boolean defaultEgressPolicy = false; + if (rules != null) { + if (rules.size() > 0) { + if (rules.get(0).getTrafficType() == FirewallRule.TrafficType.Egress && rules.get(0).getType() == FirewallRule.FirewallRuleType.System) { + systemRule = String.valueOf(FirewallRule.FirewallRuleType.System); + } + } + for (final FirewallRule rule : rules) { + _rulesDao.loadSourceCidrs((FirewallRuleVO)rule); + _rulesDao.loadDestinationCidrs((FirewallRuleVO)rule); + final FirewallRule.TrafficType traffictype = rule.getTrafficType(); + if (traffictype == FirewallRule.TrafficType.Ingress) { + final FirewallRuleTO ruleTO = new FirewallRuleTO(rule, null, null, Purpose.Ipv6Firewall, traffictype); + rulesTO.add(ruleTO); + } else if (rule.getTrafficType() == FirewallRule.TrafficType.Egress) { + final NetworkVO network = _networkDao.findById(guestNetworkId); + final NetworkOfferingVO offering = _networkOfferingDao.findById(network.getNetworkOfferingId()); + defaultEgressPolicy = offering.isEgressDefaultPolicy(); + final FirewallRuleTO ruleTO = new FirewallRuleTO(rule, null, "", Purpose.Ipv6Firewall, traffictype, defaultEgressPolicy); + rulesTO.add(ruleTO); + } + } + } + + final SetIpv6FirewallRulesCommand cmd = new SetIpv6FirewallRulesCommand(rulesTO); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, _routerControlHelper.getRouterControlIp(router.getId())); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_GUEST_IP, _routerControlHelper.getRouterIpInNetwork(guestNetworkId, router.getId())); + cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, router.getInstanceName()); + final DataCenterVO dcVo = _dcDao.findById(router.getDataCenterId()); + cmd.setAccessDetail(NetworkElementCommand.ZONE_NETWORK_TYPE, dcVo.getNetworkType().toString()); + if (systemRule != null) { + cmd.setAccessDetail(NetworkElementCommand.FIREWALL_EGRESS_DEFAULT, systemRule); + } else { + cmd.setAccessDetail(NetworkElementCommand.FIREWALL_EGRESS_DEFAULT, String.valueOf(defaultEgressPolicy)); + } + + cmds.addCommand(cmd); + } + public void createAssociateIPCommands(final VirtualRouter router, final List ips, final Commands cmds, final long vmId) { final String ipAssocCommand = "IPAssocCommand"; createRedundantAssociateIPCommands(router, ips, cmds, ipAssocCommand, false); @@ -1019,6 +1107,8 @@ public class CommandSetupHelper { String defaultDns1 = null; String defaultDns2 = null; + String defaultIp6Dns1 = null; + String defaultIp6Dns2 = null; final boolean dnsProvided = _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dns, Provider.VPCVirtualRouter); final boolean dhcpProvided = _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dhcp, Provider.VPCVirtualRouter); @@ -1037,6 +1127,8 @@ public class CommandSetupHelper { } else { defaultDns2 = dcVo.getDns2(); } + defaultIp6Dns1 = dcVo.getIp6Dns1(); + defaultIp6Dns2 = dcVo.getIp6Dns2(); } final Nic nic = _nicDao.findByNtwkIdAndInstanceId(network.getId(), router.getId()); @@ -1048,6 +1140,11 @@ public class CommandSetupHelper { final SetupGuestNetworkCommand setupCmd = new SetupGuestNetworkCommand(dhcpRange, networkDomain, router.getIsRedundantRouter(), defaultDns1, defaultDns2, add, _itMgr.toNicTO(nicProfile, router.getHypervisorType())); + NicVO publicNic = _nicDao.findDefaultNicForVM(router.getId()); + if (publicNic != null) { + updateSetupGuestNetworkCommandIpv6(setupCmd, network, publicNic, defaultIp6Dns1, defaultIp6Dns2); + } + final String brd = NetUtils.long2Ip(NetUtils.ip2Long(guestNic.getIPv4Address()) | ~NetUtils.ip2Long(guestNic.getIPv4Netmask())); setupCmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, _routerControlHelper.getRouterControlIp(router.getId())); setupCmd.setAccessDetail(NetworkElementCommand.ROUTER_GUEST_IP, _routerControlHelper.getRouterIpInNetwork(network.getId(), router.getId())); @@ -1064,6 +1161,18 @@ public class CommandSetupHelper { return setupCmd; } + private void updateSetupGuestNetworkCommandIpv6(SetupGuestNetworkCommand setupCmd, Network network, Nic nic, String defaultIp6Dns1, String defaultIp6Dns2) { + boolean isIpv6Supported = _networkOfferingDao.isIpv6Supported(network.getNetworkOfferingId()); + if (isIpv6Supported) { + setupCmd.setDefaultIp6Dns1(defaultIp6Dns1); + setupCmd.setDefaultIp6Dns2(defaultIp6Dns2); + Nic updatedNic = ipv6Service.assignPublicIpv6ToNetwork(network, nic); + setupCmd.setRouterIpv6(updatedNic.getIPv6Address()); + setupCmd.setRouterIpv6Gateway(updatedNic.getIPv6Gateway()); + setupCmd.setRouterIpv6Cidr(updatedNic.getIPv6Cidr()); + } + } + private VmDataCommand generateVmDataCommand(final VirtualRouter router, final String vmPrivateIpAddress, final String userData, final String serviceOffering, final String zoneName, final String publicIpAddress, final String vmName, final String vmInstanceName, final long vmId, final String vmUuid, final String publicKey, final long guestNetworkId, String hostname) { diff --git a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java index 6416e3cadd7..934336066cc 100644 --- a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java +++ b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java @@ -29,14 +29,13 @@ import javax.inject.Inject; import com.cloud.utils.validation.ChecksumUtil; import org.apache.cloudstack.api.ApiConstants; -import org.apache.log4j.Logger; - import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinition; import org.apache.cloudstack.utils.CloudStackVersion; +import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; @@ -66,6 +65,7 @@ import com.cloud.host.Status; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.IpAddressManager; +import com.cloud.network.Ipv6Service; import com.cloud.network.Network; import com.cloud.network.NetworkModel; import com.cloud.network.Networks.BroadcastDomainType; @@ -165,6 +165,8 @@ public class NetworkHelperImpl implements NetworkHelper { NetworkDetailsDao networkDetailsDao; @Inject RouterHealthCheckResultDao _routerHealthCheckResultDao; + @Inject + Ipv6Service ipv6Service; protected final Map> hypervisorsMap = new HashMap<>(); @@ -663,7 +665,7 @@ public class NetworkHelperImpl implements NetworkHelper { return controlConfig; } - protected LinkedHashMap> configurePublicNic(final RouterDeploymentDefinition routerDeploymentDefinition, final boolean hasGuestNic) { + protected LinkedHashMap> configurePublicNic(final RouterDeploymentDefinition routerDeploymentDefinition, final boolean hasGuestNic) throws InsufficientAddressCapacityException { final LinkedHashMap> publicConfig = new LinkedHashMap>(3); if (routerDeploymentDefinition.isPublicNetwork()) { @@ -704,6 +706,9 @@ public class NetworkHelperImpl implements NetworkHelper { s_logger.info("Use same MAC as previous RvR, the MAC is " + peerNic.getMacAddress()); defaultNic.setMacAddress(peerNic.getMacAddress()); } + if (routerDeploymentDefinition.getGuestNetwork() != null) { + ipv6Service.updateNicIpv6(defaultNic, routerDeploymentDefinition.getDest().getDataCenter(), routerDeploymentDefinition.getGuestNetwork()); + } publicConfig.put(publicNetworks.get(0), new ArrayList(Arrays.asList(defaultNic))); } diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index 2512e9c5206..5ce43629087 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -71,6 +71,7 @@ import org.apache.cloudstack.network.topology.NetworkTopologyContext; import org.apache.cloudstack.utils.CloudStackVersion; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.cloudstack.utils.usage.UsageUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; @@ -1983,12 +1984,12 @@ Configurable, StateListener vpns = new ArrayList(); final List pfRules = new ArrayList(); @@ -2580,7 +2600,7 @@ Configurable, StateListener rules, final long networkId) { + final NetworkVO network = _networkDao.findById(networkId); + if(!_networkOfferingDao.isIpv6Supported(network.getNetworkOfferingId())) { + return; + } + // Since not all networks will IPv6 supported, add a system rule for IPv6 networks + final List sourceCidr = new ArrayList(); + final List destCidr = new ArrayList(); + sourceCidr.add(network.getIp6Cidr()); + destCidr.add(NetUtils.ALL_IP6_CIDRS); + final FirewallRule rule = new FirewallRuleVO(null, null, null, null, NetUtils.ALL_PROTO, networkId, network.getAccountId(), network.getDomainId(), Purpose.Ipv6Firewall, sourceCidr, + destCidr, null, null, null, FirewallRule.TrafficType.Egress, FirewallRule.FirewallRuleType.System); + rules.add(rule); + } + private void removeRevokedIpAliasFromDb(final List revokedIpAliasVOs) { for (final NicIpAliasVO ipalias : revokedIpAliasVOs) { _nicIpAliasDao.expunge(ipalias.getId()); diff --git a/server/src/main/java/com/cloud/network/vpc/NetworkACLManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/NetworkACLManagerImpl.java index 55e65689de4..fa9385a774e 100644 --- a/server/src/main/java/com/cloud/network/vpc/NetworkACLManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/NetworkACLManagerImpl.java @@ -48,6 +48,7 @@ import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; public class NetworkACLManagerImpl extends ManagerBase implements NetworkACLManager { private static final Logger s_logger = Logger.getLogger(NetworkACLManagerImpl.class); @@ -75,6 +76,15 @@ public class NetworkACLManagerImpl extends ManagerBase implements NetworkACLMana private List _networkAclElements; + private boolean containsIpv6Cidr(List cidrs) { + for (String cidr : cidrs) { + if (NetUtils.isValidIp6Cidr(cidr)) { + return true; + } + } + return false; + } + @Override public NetworkACL createNetworkACL(final String name, final String description, final long vpcId, final Boolean forDisplay) { final NetworkACLVO acl = new NetworkACLVO(name, description, vpcId); @@ -339,7 +349,8 @@ public class NetworkACLManagerImpl extends ManagerBase implements NetworkACLMana if (network.getNetworkACLId() == null) { return null; } - return _networkACLItemDao.listByACL(network.getNetworkACLId()); + List rules = _networkACLItemDao.listByACL(network.getNetworkACLId()); + return rules; } private void removeRule(final NetworkACLItem rule) { diff --git a/server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java b/server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java index 0b3d4a86a4e..45a39db96a4 100644 --- a/server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java @@ -608,7 +608,7 @@ public class NetworkACLServiceImpl extends ManagerBase implements NetworkACLServ List sourceCidrList = networkACLItemVO.getSourceCidrList(); if (CollectionUtils.isNotEmpty(sourceCidrList)) { for (String cidr : sourceCidrList) { - if (!NetUtils.isValidIp4Cidr(cidr)) { + if (!NetUtils.isValidIp4Cidr(cidr) && !NetUtils.isValidIp6Cidr(cidr)) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Source cidrs formatting error " + cidr); } } diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index eda1d2d4d80..781ee38c8a4 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -84,6 +84,7 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.IpAddress; import com.cloud.network.IpAddressManager; +import com.cloud.network.Ipv6Service; import com.cloud.network.Network; import com.cloud.network.Network.Capability; import com.cloud.network.Network.GuestType; @@ -184,6 +185,8 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @Inject NetworkDao _ntwkDao; @Inject + NetworkOfferingDao networkOfferingDao; + @Inject NetworkOrchestrationService _ntwkMgr; @Inject NetworkModel _ntwkModel; @@ -375,7 +378,8 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis final String displayText = cmd.getDisplayText(); final List supportedServices = cmd.getSupportedServices(); final Map> serviceProviderList = cmd.getServiceProviders(); - final Map serviceCapabilitystList = cmd.getServiceCapabilitystList(); + final Map serviceCapabilityList = cmd.getServiceCapabilityList(); + final NetUtils.InternetProtocol internetProtocol = NetUtils.InternetProtocol.fromValue(cmd.getInternetProtocol()); final Long serviceOfferingId = cmd.getServiceOfferingId(); final List domainIds = cmd.getDomainIds(); final List zoneIds = cmd.getZoneIds(); @@ -398,14 +402,18 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis } return createVpcOffering(vpcOfferingName, displayText, supportedServices, - serviceProviderList, serviceCapabilitystList, serviceOfferingId, + serviceProviderList, serviceCapabilityList, internetProtocol, serviceOfferingId, domainIds, zoneIds, (enable ? State.Enabled : State.Disabled)); } @Override @ActionEvent(eventType = EventTypes.EVENT_VPC_OFFERING_CREATE, eventDescription = "creating vpc offering", create = true) public VpcOffering createVpcOffering(final String name, final String displayText, final List supportedServices, final Map> serviceProviders, - final Map serviceCapabilitystList, final Long serviceOfferingId, List domainIds, List zoneIds, State state) { + final Map serviceCapabilityList, final NetUtils.InternetProtocol internetProtocol, final Long serviceOfferingId, List domainIds, List zoneIds, State state) { + + if (!Ipv6Service.Ipv6OfferingCreationEnabled.value() && !(internetProtocol == null || NetUtils.InternetProtocol.IPv4.equals(internetProtocol))) { + throw new InvalidParameterValueException(String.format("Configuration %s needs to be enabled for creating IPv6 supported VPC offering", Ipv6Service.Ipv6OfferingCreationEnabled.key())); + } // Filter child domains when both parent and child domains are present List filteredDomainIds = filterChildSubDomains(domainIds); @@ -480,11 +488,11 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis svcProviderMap.put(Service.Gateway, sourceNatServiceProviders); } - validateConnectivtyServiceCapabilities(svcProviderMap.get(Service.Connectivity), serviceCapabilitystList); + validateConnectivtyServiceCapabilities(svcProviderMap.get(Service.Connectivity), serviceCapabilityList); - final boolean supportsDistributedRouter = isVpcOfferingSupportsDistributedRouter(serviceCapabilitystList); - final boolean offersRegionLevelVPC = isVpcOfferingForRegionLevelVpc(serviceCapabilitystList); - final boolean redundantRouter = isVpcOfferingRedundantRouter(serviceCapabilitystList); + final boolean supportsDistributedRouter = isVpcOfferingSupportsDistributedRouter(serviceCapabilityList); + final boolean offersRegionLevelVPC = isVpcOfferingForRegionLevelVpc(serviceCapabilityList); + final boolean redundantRouter = isVpcOfferingRedundantRouter(serviceCapabilityList); final VpcOfferingVO offering = createVpcOffering(name, displayText, svcProviderMap, false, state, serviceOfferingId, supportsDistributedRouter, offersRegionLevelVPC, redundantRouter); @@ -498,6 +506,9 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis detailsVO.add(new VpcOfferingDetailsVO(offering.getId(), ApiConstants.ZONE_ID, String.valueOf(zoneId), false)); } } + if (internetProtocol != null) { + detailsVO.add(new VpcOfferingDetailsVO(offering.getId(), ApiConstants.INTERNET_PROTOCOL, String.valueOf(internetProtocol), true)); + } if (!detailsVO.isEmpty()) { vpcOfferingDetailsDao.saveDetails(detailsVO); } @@ -2676,7 +2687,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis @Override public Network createVpcGuestNetwork(final long ntwkOffId, final String name, final String displayText, final String gateway, final String cidr, final String vlanId, String networkDomain, final Account owner, final Long domainId, final PhysicalNetwork pNtwk, final long zoneId, final ACLType aclType, final Boolean subdomainAccess, - final long vpcId, final Long aclId, final Account caller, final Boolean isDisplayNetworkEnabled, String externalId) throws ConcurrentOperationException, InsufficientCapacityException, + final long vpcId, final Long aclId, final Account caller, final Boolean isDisplayNetworkEnabled, String externalId, String ip6Gateway, String ip6Cidr) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { final Vpc vpc = getActiveVpc(vpcId); @@ -2701,7 +2712,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis // 2) Create network final Network guestNetwork = _ntwkMgr.createGuestNetwork(ntwkOffId, name, displayText, gateway, cidr, vlanId, false, networkDomain, owner, domainId, pNtwk, zoneId, aclType, - subdomainAccess, vpcId, null, null, isDisplayNetworkEnabled, null, null, externalId, null, null); + subdomainAccess, vpcId, ip6Gateway, ip6Cidr, isDisplayNetworkEnabled, null, null, externalId, null, null); if (guestNetwork != null) { guestNetwork.setNetworkACLId(aclId); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 899f27d79af..f16443e7c85 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -2343,7 +2343,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir List nics = _nicDao.listByNetworkId(network.getId()); for (NicVO nic : nics) { - if (nic.getIPv4Address() == null) { long nicId = nic.getId(); long vmId = nic.getInstanceId(); diff --git a/server/src/main/java/org/apache/cloudstack/network/topology/BasicNetworkVisitor.java b/server/src/main/java/org/apache/cloudstack/network/topology/BasicNetworkVisitor.java index d7a94faac0c..c25c80f5fc2 100644 --- a/server/src/main/java/org/apache/cloudstack/network/topology/BasicNetworkVisitor.java +++ b/server/src/main/java/org/apache/cloudstack/network/topology/BasicNetworkVisitor.java @@ -150,6 +150,12 @@ public class BasicNetworkVisitor extends NetworkTopologyVisitor { return _networkGeneralHelper.sendCommandsToRouter(router, cmds); + } else if (purpose == Purpose.Ipv6Firewall) { + + _commandSetupHelper.createApplyIpv6FirewallRulesCommands(rules, router, cmds, network.getId()); + + return _networkGeneralHelper.sendCommandsToRouter(router, cmds); + } s_logger.warn("Unable to apply rules of purpose: " + rules.get(0).getPurpose()); diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index 0b7d55d58c5..38cdf3c99bd 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -139,6 +139,8 @@ + + diff --git a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java index 81831e3667a..55944d67efa 100644 --- a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -25,10 +25,13 @@ import javax.naming.ConfigurationException; import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; +import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd; import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; +import org.apache.cloudstack.api.command.admin.network.DeleteGuestNetworkIpv6PrefixCmd; import org.apache.cloudstack.api.command.admin.network.DeleteManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd; +import org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6PrefixesCmd; import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd; @@ -61,6 +64,7 @@ import com.cloud.configuration.ConfigurationService; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.DataCenterGuestIpv6Prefix; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; import com.cloud.dc.Pod; @@ -86,6 +90,7 @@ import com.cloud.org.Grouping.AllocationState; import com.cloud.user.Account; import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.net.NetUtils; @Component public class MockConfigurationManagerImpl extends ManagerBase implements ConfigurationManager, ConfigurationService { @@ -214,6 +219,33 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu return; } + /* (non-Javadoc) + * @see com.cloud.configuration.ConfigurationService#createDataCenterGuestIpv6Prefix(org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd) + */ + @Override + public DataCenterGuestIpv6Prefix createDataCenterGuestIpv6Prefix(CreateGuestNetworkIpv6PrefixCmd cmd) { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see com.cloud.configuration.ConfigurationService#listDataCenterGuestIpv6Prefixes(org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6PrefixesCmd) + */ + @Override + public List listDataCenterGuestIpv6Prefixes(ListGuestNetworkIpv6PrefixesCmd cmd) { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see com.cloud.configuration.ConfigurationService#deleteDataCenterGuestIpv6Prefix(org.apache.cloudstack.api.command.admin.network.DeleteGuestNetworkIpv6RangeCmd) + */ + @Override + public boolean deleteDataCenterGuestIpv6Prefix(DeleteGuestNetworkIpv6PrefixCmd cmd) { + // TODO Auto-generated method stub + return true; + } + /* (non-Javadoc) * @see com.cloud.configuration.ConfigurationService#editPod(org.apache.cloudstack.api.commands.UpdatePodCmd) */ @@ -508,7 +540,8 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu public NetworkOfferingVO createNetworkOffering(String name, String displayText, TrafficType trafficType, String tags, boolean specifyVlan, Availability availability, Integer networkRate, Map> serviceProviderMap, boolean isDefault, GuestType type, boolean systemOnly, Long serviceOfferingId, boolean conserveMode, Map> serviceCapabilityMap, boolean specifyIpRanges, boolean isPersistent, - Map details, boolean egressDefaultPolicy, Integer maxconn, boolean enableKeepAlive, Boolean forVpc, List domainIds, List zoneIds, boolean enableOffering) { + Map details, boolean egressDefaultPolicy, Integer maxconn, boolean enableKeepAlive, Boolean forVpc, + List domainIds, List zoneIds, boolean enableOffering, NetUtils.InternetProtocol internetProtocol) { // TODO Auto-generated method stub return null; } diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index 0ba9dfa3ab9..09f00b7925f 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -885,6 +885,12 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches return null; } + @Override + public Nic savePlaceholderNic(Network network, String ip4Address, String ip6Address, String ip6Cidr, String ip6Gateway, String reserver, Type vmType) { + // TODO Auto-generated method stub + return null; + } + @Override public DhcpServiceProvider getDhcpServiceProvider(Network network) { return null; //To change body of implemented methods use File | Settings | File Templates. diff --git a/server/src/test/java/com/cloud/vpc/NetworkACLManagerTest.java b/server/src/test/java/com/cloud/vpc/NetworkACLManagerTest.java index 5904b433725..4ac714fd005 100644 --- a/server/src/test/java/com/cloud/vpc/NetworkACLManagerTest.java +++ b/server/src/test/java/com/cloud/vpc/NetworkACLManagerTest.java @@ -72,6 +72,7 @@ import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.VpcService; import com.cloud.network.vpc.dao.NetworkACLDao; import com.cloud.network.vpc.dao.VpcGatewayDao; +import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.tags.dao.ResourceTagDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; @@ -100,6 +101,8 @@ public class NetworkACLManagerTest extends TestCase { @Inject NetworkDao _networkDao; @Inject + NetworkOfferingDao networkOfferingDao; + @Inject ConfigurationManager _configMgr; @Inject EntityManager _entityMgr; @@ -144,6 +147,7 @@ public class NetworkACLManagerTest extends TestCase { public void testApplyACL() throws Exception { final NetworkVO network = Mockito.mock(NetworkVO.class); Mockito.when(_networkDao.findById(anyLong())).thenReturn(network); + Mockito.when(networkOfferingDao.isIpv6Supported(anyLong())).thenReturn(false); Mockito.when(_networkModel.isProviderSupportServiceInNetwork(anyLong(), Matchers.any(Network.Service.class), Matchers.any(Network.Provider.class))).thenReturn(true); Mockito.when(_networkAclElements.get(0).applyNetworkACLs(Matchers.any(Network.class), Matchers.anyList())).thenReturn(true); assertTrue(_aclMgr.applyACLToNetwork(1L)); @@ -175,6 +179,7 @@ public class NetworkACLManagerTest extends TestCase { when(ntwkSrvcDao.canProviderSupportServiceInNetwork(anyLong(), eq(Network.Service.NetworkACL), nullable(Network.Provider.class))).thenReturn(true); Mockito.when(_networkDao.listByAclId(anyLong())).thenReturn(networks); Mockito.when(_networkDao.findById(anyLong())).thenReturn(network); + Mockito.when(networkOfferingDao.isIpv6Supported(anyLong())).thenReturn(false); Mockito.when(_networkModel.isProviderSupportServiceInNetwork(anyLong(), any(Network.Service.class), any(Network.Provider.class))).thenReturn(true); Mockito.when(_networkAclElements.get(0).getProvider()).thenReturn(Mockito.mock(Network.Provider.class)); Mockito.when(_networkAclElements.get(0).applyNetworkACLs(any(Network.class), anyList())).thenReturn(applyNetworkACLs); @@ -298,6 +303,11 @@ public class NetworkACLManagerTest extends TestCase { return Mockito.mock(NetworkDao.class); } + @Bean + public NetworkOfferingDao networkOfferingDao() { + return Mockito.mock(NetworkOfferingDao.class); + } + @Bean public ConfigurationManager configMgr() { return Mockito.mock(ConfigurationManager.class); diff --git a/server/src/test/java/com/cloud/vpc/dao/MockVpcOfferingDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockVpcOfferingDaoImpl.java index 9b35ad8a976..f0ff4822476 100644 --- a/server/src/test/java/com/cloud/vpc/dao/MockVpcOfferingDaoImpl.java +++ b/server/src/test/java/com/cloud/vpc/dao/MockVpcOfferingDaoImpl.java @@ -21,6 +21,7 @@ import com.cloud.network.vpc.VpcOfferingVO; import com.cloud.network.vpc.dao.VpcOfferingDao; import com.cloud.utils.db.DB; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.net.NetUtils; @DB() public class MockVpcOfferingDaoImpl extends GenericDaoBase implements VpcOfferingDao { @@ -38,4 +39,13 @@ public class MockVpcOfferingDaoImpl extends GenericDaoBase return vo; } + @Override + public NetUtils.InternetProtocol getVpcOfferingInternetProtocol(long offeringId) { + return null; + } + + @Override + public boolean isIpv6Supported(long offeringId) { + return false; + } } diff --git a/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java b/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java index caf2b910a25..16a03646708 100644 --- a/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java +++ b/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java @@ -132,7 +132,7 @@ public class CreateNetworkOfferingTest extends TestCase { public void createSharedNtwkOffWithVlan() { NetworkOfferingVO off = configMgr.createNetworkOffering("shared", "shared", TrafficType.Guest, null, true, Availability.Optional, 200, null, false, Network.GuestType.Shared, false, - null, false, null, true, false, null, false, null, true, false, null, null, false); + null, false, null, true, false, null, false, null, true, false, null, null, false, null); assertNotNull("Shared network offering with specifyVlan=true failed to create ", off); } @@ -140,7 +140,7 @@ public class CreateNetworkOfferingTest extends TestCase { public void createSharedNtwkOffWithNoVlan() { NetworkOfferingVO off = configMgr.createNetworkOffering("shared", "shared", TrafficType.Guest, null, false, Availability.Optional, 200, null, false, Network.GuestType.Shared, - false, null, false, null, true, false, null, false, null, true, false, null, null, false); + false, null, false, null, true, false, null, false, null, true, false, null, null, false, null); assertNotNull("Shared network offering with specifyVlan=false was created", off); } @@ -148,7 +148,7 @@ public class CreateNetworkOfferingTest extends TestCase { public void createSharedNtwkOffWithSpecifyIpRanges() { NetworkOfferingVO off = configMgr.createNetworkOffering("shared", "shared", TrafficType.Guest, null, true, Availability.Optional, 200, null, false, Network.GuestType.Shared, false, - null, false, null, true, false, null, false, null, true, false, null, null, false); + null, false, null, true, false, null, false, null, true, false, null, null, false, null); assertNotNull("Shared network offering with specifyIpRanges=true failed to create ", off); } @@ -157,7 +157,7 @@ public class CreateNetworkOfferingTest extends TestCase { public void createSharedNtwkOffWithoutSpecifyIpRanges() { NetworkOfferingVO off = configMgr.createNetworkOffering("shared", "shared", TrafficType.Guest, null, true, Availability.Optional, 200, null, false, Network.GuestType.Shared, - false, null, false, null, false, false, null, false, null, true, false, null, null, false); + false, null, false, null, false, false, null, false, null, true, false, null, null, false, null); assertNull("Shared network offering with specifyIpRanges=false was created", off); } @@ -170,7 +170,7 @@ public class CreateNetworkOfferingTest extends TestCase { serviceProviderMap.put(Network.Service.SourceNat, vrProvider); NetworkOfferingVO off = configMgr.createNetworkOffering("isolated", "isolated", TrafficType.Guest, null, false, Availability.Optional, 200, serviceProviderMap, false, - Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, false, null, null, false); + Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, false, null, null, false, null); assertNotNull("Isolated network offering with specifyIpRanges=false failed to create ", off); } @@ -183,7 +183,7 @@ public class CreateNetworkOfferingTest extends TestCase { serviceProviderMap.put(Network.Service.SourceNat, vrProvider); NetworkOfferingVO off = configMgr.createNetworkOffering("isolated", "isolated", TrafficType.Guest, null, true, Availability.Optional, 200, serviceProviderMap, false, - Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, false, null, null, false); + Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, false, null, null, false, null); assertNotNull("Isolated network offering with specifyVlan=true wasn't created", off); } @@ -196,7 +196,7 @@ public class CreateNetworkOfferingTest extends TestCase { serviceProviderMap.put(Network.Service.SourceNat, vrProvider); NetworkOfferingVO off = configMgr.createNetworkOffering("isolated", "isolated", TrafficType.Guest, null, false, Availability.Optional, 200, serviceProviderMap, false, - Network.GuestType.Isolated, false, null, false, null, true, false, null, false, null, true, false, null, null, false); + Network.GuestType.Isolated, false, null, false, null, true, false, null, false, null, true, false, null, null, false, null); assertNull("Isolated network offering with specifyIpRanges=true and source nat service enabled, was created", off); } @@ -207,7 +207,7 @@ public class CreateNetworkOfferingTest extends TestCase { Set vrProvider = new HashSet(); NetworkOfferingVO off = configMgr.createNetworkOffering("isolated", "isolated", TrafficType.Guest, null, false, Availability.Optional, 200, serviceProviderMap, false, - Network.GuestType.Isolated, false, null, false, null, true, false, null, false, null, true, false, null, null, false); + Network.GuestType.Isolated, false, null, false, null, true, false, null, false, null, true, false, null, null, false, null); assertNotNull("Isolated network offering with specifyIpRanges=true and with no sourceNatService, failed to create", off); } @@ -225,7 +225,7 @@ public class CreateNetworkOfferingTest extends TestCase { serviceProviderMap.put(Network.Service.Lb, vrProvider); NetworkOfferingVO off = configMgr.createNetworkOffering("isolated", "isolated", TrafficType.Guest, null, true, Availability.Optional, 200, serviceProviderMap, false, - Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, null, null, false); + Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, null, null, false, null); // System.out.println("Creating Vpc Network Offering"); assertNotNull("Vpc Isolated network offering with Vpc provider ", off); } @@ -245,7 +245,7 @@ public class CreateNetworkOfferingTest extends TestCase { serviceProviderMap.put(Network.Service.Lb, lbProvider); NetworkOfferingVO off = configMgr.createNetworkOffering("isolated", "isolated", TrafficType.Guest, null, true, Availability.Optional, 200, serviceProviderMap, false, - Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, null, null, false); + Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, null, null, false, null); // System.out.println("Creating Vpc Network Offering"); assertNotNull("Vpc Isolated network offering with Vpc and Netscaler provider ", off); } diff --git a/server/src/test/resources/createNetworkOffering.xml b/server/src/test/resources/createNetworkOffering.xml index 897d4dac305..623cfaca66b 100644 --- a/server/src/test/resources/createNetworkOffering.xml +++ b/server/src/test/resources/createNetworkOffering.xml @@ -41,6 +41,13 @@ + + + + + + + diff --git a/systemvm/debian/etc/radvd.conf.tmpl b/systemvm/debian/etc/radvd.conf.tmpl new file mode 100644 index 00000000000..0001c9df401 --- /dev/null +++ b/systemvm/debian/etc/radvd.conf.tmpl @@ -0,0 +1,12 @@ +interface {{ GUEST_INTERFACE }} +{ + AdvSendAdvert on; + MinRtrAdvInterval 5; + MaxRtrAdvInterval 15; + prefix {{ IPV6_CIDR }} + { + AdvOnLink on; + AdvAutonomous on; + }; +{{ RDNSS_CONFIG }} +}; diff --git a/systemvm/debian/opt/cloud/bin/checkrouter.sh b/systemvm/debian/opt/cloud/bin/checkrouter.sh index ae3aff7eb10..c3a73e9e5e3 100755 --- a/systemvm/debian/opt/cloud/bin/checkrouter.sh +++ b/systemvm/debian/opt/cloud/bin/checkrouter.sh @@ -27,10 +27,10 @@ fi ROUTER_TYPE=$(cat /etc/cloudstack/cmdline.json | grep type | awk '{print $2;}' | sed -e 's/[,\"]//g') if [ "$ROUTER_TYPE" = "router" ] then - ROUTER_STATE=$(ip addr show dev eth0 | grep inet | wc -l | xargs bash -c 'if [ $0 == 2 ]; then echo "PRIMARY"; else echo "BACKUP"; fi') + ROUTER_STATE=$(ip -4 addr show dev eth0 | grep inet | wc -l | xargs bash -c 'if [ $0 == 2 ]; then echo "PRIMARY"; else echo "BACKUP"; fi') STATUS=$ROUTER_STATE else - ROUTER_STATE=$(ip addr show dev eth1 | grep state | awk '{print $9;}') + ROUTER_STATE=$(ip -4 addr show dev eth1 | grep state | awk '{print $9;}') if [ "$ROUTER_STATE" = "UP" ] then STATUS=PRIMARY diff --git a/systemvm/debian/opt/cloud/bin/configure.py b/systemvm/debian/opt/cloud/bin/configure.py index d9b779d7fa6..e6c608b4699 100755 --- a/systemvm/debian/opt/cloud/bin/configure.py +++ b/systemvm/debian/opt/cloud/bin/configure.py @@ -24,6 +24,7 @@ import sys import urllib import urllib2 import time +import copy from collections import OrderedDict from fcntl import flock, LOCK_EX, LOCK_UN @@ -38,7 +39,36 @@ from cs.CsLoadBalancer import CsLoadBalancer from cs.CsConfig import CsConfig from cs.CsProcess import CsProcess from cs.CsStaticRoutes import CsStaticRoutes +from cs.CsVpcGuestNetwork import CsVpcGuestNetwork +ICMPV6_TYPE_ANY = "{ destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-done, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, router-renumbering }" +TCP_UDP_PORT_ANY = "{ 0-65535 }" + +def removeUndesiredCidrs(cidrs, version): + version_char = ":" + if version == 4: + version_char = "." + if "," in cidrs: + cidrList = cidrs.split(",") + ipv4Cidrs = [] + for cidr in cidrList: + if version_char not in cidr: + ipv4Cidrs.append(cidr) + if len(ipv4Cidrs) > 0: + return ",".join(ipv4Cidrs) + else: + if version_char not in cidrs: + return cidrs + return None + +def appendStringIfNotEmpty(s1, s2): + if s2: + if type(s2) != str: + s2 = str(s2) + if s1: + return s1 + " " + s2 + return s2 + return s1 class CsPassword(CsDataBag): @@ -244,6 +274,9 @@ class CsAcl(CsDataBag): self.egress = [] self.device = obj['device'] self.ip = obj['nic_ip'] + self.ip6_cidr = None + if "nic_ip6_cidr" in obj.keys(): + self.ip6_cidr = obj['nic_ip6_cidr'] self.netmask = obj['nic_netmask'] self.config = config self.cidr = "%s/%s" % (self.ip, self.netmask) @@ -252,18 +285,108 @@ class CsAcl(CsDataBag): if "egress_rules" in obj.keys(): self.egress = obj['egress_rules'] self.fw = config.get_fw() + self.ipv6_acl = config.get_ipv6_acl() def create(self): self.process("ingress", self.ingress, self.FIXED_RULES_INGRESS) self.process("egress", self.egress, self.FIXED_RULES_EGRESS) + def __process_ip6(self, direction, rule_list): + if not self.ip6_cidr: + return + tier_cidr = self.ip6_cidr + chain = "%s_%s_policy" % (self.device, direction) + rule = "accept" + parent_chain = "acl_output" + cidr_key = "saddr" + parent_chain_rule = "ip6 saddr ::/0 jump %s" % (chain) + if direction == "ingress": + rule = "drop" + parent_chain = "acl_input" + cidr_key = "daddr" + parent_chain_rule = "ip6 %s %s jump %s" % (cidr_key, tier_cidr, chain) + self.ipv6_acl.append({'type': "", 'chain': parent_chain, 'rule': parent_chain_rule}) + self.ipv6_acl.insert(0, {'type': "chain", 'chain': chain, 'rule': rule}) + for rule in rule_list: + cidr = rule['cidr'] + if cidr != None and cidr != "": + cidr = removeUndesiredCidrs(cidr, 4) + if cidr == None or cidr == "": + continue + addr = "" + if cidr: + addr = "ip6 daddr " + cidr + if direction == "ingress": + addr = "ip6 saddr " + cidr + + proto = "" + protocol = rule['type'] + if protocol != "all": + icmp_type = "" + if protocol == "protocol": + protocol = "ip6 nexthdr %d" % rule['protocol'] + proto = protocol + if proto == "icmp": + proto = proto_str = "icmpv6" + icmp_type = ICMPV6_TYPE_ANY + if 'icmp_type' in rule and rule['icmp_type'] != -1: + icmp_type = str(rule['icmp_type']) + proto = "%s type %s" % (proto_str, icmp_type) + if 'icmp_code' in rule and rule['icmp_code'] != -1: + proto = "%s %s code %d" % (proto, proto_str, rule['icmp_code']) + + first_port = "" + last_port = "" + if 'first_port' in rule: + first_port = rule['first_port'] + if 'last_port' in rule: + last_port = rule['last_port'] + port = "" + if first_port: + port = first_port + if last_port and port and \ + last_port != first_port: + port = "{%s-%s}" % (port, last_port) + if (protocol == "tcp" or protocol == "udp") and not port: + port = TCP_UDP_PORT_ANY + if port: + proto = "%s dport %s" % (proto, port) + + action = "drop" + if 'allowed' in rule.keys() and rule['allowed']: + action = "accept" + + rstr = addr + type = "" + rstr = appendStringIfNotEmpty(rstr, proto) + if rstr and action: + rstr = rstr + " " + action + else: + type = "chain" + rstr = action + logging.debug("Process IPv6 ACL rule %s" % rstr) + if type == "chain": + self.ipv6_acl.insert(0, {'type': type, 'chain': chain, 'rule': rstr}) + else: + self.ipv6_acl.append({'type': type, 'chain': chain, 'rule': rstr}) + def process(self, direction, rule_list, base): count = base for i in rule_list: - r = self.AclRule(direction, self, i, self.config, count) + ruleData = copy.copy(i) + cidr = ruleData['cidr'] + if cidr != None and cidr != "": + cidr = removeUndesiredCidrs(cidr, 6) + if cidr == None or cidr == "": + continue + ruleData['cidr'] = cidr + r = self.AclRule(direction, self, ruleData, self.config, count) r.create() count += 1 + # Prepare IPv6 ACL rules + self.__process_ip6(direction, rule_list) + class AclRule(): def __init__(self, direction, acl, rule, config, count): @@ -323,6 +446,14 @@ class CsAcl(CsDataBag): CsHelper.execute("ipset -L | grep Name: | awk {'print $2'} | ipset flush") CsHelper.execute("ipset -L | grep Name: | awk {'print $2'} | ipset destroy") + def flushAllIpv6Rules(self): + logging.info("Flush all IPv6 ACL rules") + address_family = 'ip6' + table = 'ip6_acl' + tables = CsHelper.execute("nft list tables %s | grep %s" % (address_family, table)) + if any(table in t for t in tables): + CsHelper.execute("nft delete table %s %s" % (address_family, table)) + def process(self): for item in self.dbag: if item == "id": @@ -333,6 +464,113 @@ class CsAcl(CsDataBag): self.AclIP(self.dbag[item], self.config).create() +class CsIpv6Firewall(CsDataBag): + """ + Deal with IPv6 Firewall + """ + + def flushAllRules(self): + logging.info("Flush all IPv6 firewall rules") + address_family = 'ip6' + table = 'ip6_firewall' + tables = CsHelper.execute("nft list tables %s | grep %s" % (address_family, table)) + if any(table in t for t in tables): + CsHelper.execute("nft delete table %s %s" % (address_family, table)) + + def process(self): + fw = self.config.get_ipv6_fw() + logging.info("Processing IPv6 firewall rules %s; %s" % (self.dbag, fw)) + for item in self.dbag: + if item == "id": + continue + rule = self.dbag[item] + rstr = "" + + chain = "fw_chain_ingress" + if 'traffic_type' in rule and rule['traffic_type'].lower() == "egress": + chain = "fw_chain_egress" + + saddr = "" + if 'source_cidr_list' in rule and len(rule['source_cidr_list']) > 0: + source_cidrs = rule['source_cidr_list'] + if len(source_cidrs) == 1: + source_cidrs = source_cidrs[0] + else: + source_cidrs = "{" + (",".join(source_cidrs)) + "}" + saddr = "ip6 saddr " + source_cidrs + daddr = "" + if 'dest_cidr_list' in rule and len(rule['dest_cidr_list']) > 0: + dest_cidrs = rule['dest_cidr_list'] + if len(dest_cidrs) == 1: + dest_cidrs = dest_cidrs[0] + else: + dest_cidrs = "{" + (",".join(dest_cidrs)) + "}" + daddr = "ip6 daddr " + dest_cidrs + + proto = "" + protocol = rule['protocol'] + if protocol != "all": + icmp_type = "" + proto = protocol + if proto == "icmp": + proto = proto_str = "icmpv6" + icmp_type = ICMPV6_TYPE_ANY + if 'icmp_type' in rule and rule['icmp_type'] != -1: + icmp_type = str(rule['icmp_type']) + proto = "%s type %s" % (proto_str, icmp_type) + if 'icmp_code' in rule and rule['icmp_code'] != -1: + proto = "%s %s code %d" % (proto, proto_str, rule['icmp_code']) + first_port = "" + last_port = "" + if 'src_port_range' in rule: + first_port = rule['src_port_range'][0] + last_port = rule['src_port_range'][1] + port = "" + if first_port: + port = first_port + if last_port and port and \ + last_port != first_port: + port = "{%s-%s}" % (port, last_port) + if (protocol == "tcp" or protocol == "udp") and not port: + port = TCP_UDP_PORT_ANY + if port: + proto = "%s dport %s" % (proto, port) + + action = "accept" + if chain == "fw_chain_egress": + # In case we have a default rule (accept all or drop all), we have to evaluate the action again. + if protocol == 'all' and not rule['source_cidr_list']: + # For default egress ALLOW or DENY, the logic is inverted. + # Having default_egress_policy == True, means that the default rule should have ACCEPT, + # otherwise DROP. The rule should be appended, not inserted. + if rule['default_egress_policy']: + action = "accept" + else: + action = "drop" + else: + # For other rules added, if default_egress_policy == True, following rules should be DROP, + # otherwise ACCEPT + if rule['default_egress_policy']: + action = "drop" + else: + action = "accept" + + rstr = saddr + type = "" + rstr = appendStringIfNotEmpty(rstr, daddr) + rstr = appendStringIfNotEmpty(rstr, proto) + if rstr and action: + rstr = rstr + " " + action + else: + type = "chain" + rstr = action + logging.debug("Process IPv6 firewall rule %s" % rstr) + if type == "chain": + fw.insert(0, {'type': type, 'chain': chain, 'rule': rstr}) + else: + fw.append({'type': type, 'chain': chain, 'rule': rstr}) + + class CsVmMetadata(CsDataBag): def process(self): @@ -1039,12 +1277,17 @@ class IpTablesExecutor: def process(self): acls = CsAcl('networkacl', self.config) + acls.flushAllIpv6Rules() acls.process() acls = CsAcl('firewallrules', self.config) acls.flushAllowAllEgressRules() acls.process() + ip6_fw = CsIpv6Firewall('ipv6firewallrules', self.config) + ip6_fw.flushAllRules() + ip6_fw.process() + fwd = CsForwardingRules("forwardingrules", self.config) fwd.process() @@ -1061,6 +1304,14 @@ class IpTablesExecutor: nf = CsNetfilters() nf.compare(self.config.get_fw()) + logging.info("Configuring nftables ACL rules %s" % self.config.get_ipv6_acl()) + nf = CsNetfilters() + nf.apply_ip6_rules(self.config.get_ipv6_acl(), "acl") + + logging.info("Configuring nftables IPv6 rules %s" % self.config.get_ipv6_fw()) + nf = CsNetfilters() + nf.apply_ip6_rules(self.config.get_ipv6_fw(), "firewall") + logging.debug("Configuring iptables rules done ...saving rules") # Save iptables configuration - will be loaded on reboot by the iptables-restore that is configured on /etc/rc.local @@ -1088,24 +1339,28 @@ def main(argv): config.address().compare() config.address().process() - databag_map = OrderedDict([("guest_network", {"process_iptables": True, "executor": []}), - ("ip_aliases", {"process_iptables": True, "executor": []}), - ("vm_password", {"process_iptables": False, "executor": [CsPassword("vmpassword", config)]}), - ("vm_metadata", {"process_iptables": False, "executor": [CsVmMetadata('vmdata', config)]}), - ("network_acl", {"process_iptables": True, "executor": []}), - ("firewall_rules", {"process_iptables": True, "executor": []}), - ("forwarding_rules", {"process_iptables": True, "executor": []}), - ("staticnat_rules", {"process_iptables": True, "executor": []}), - ("site_2_site_vpn", {"process_iptables": True, "executor": []}), - ("remote_access_vpn", {"process_iptables": True, "executor": []}), - ("vpn_user_list", {"process_iptables": False, "executor": [CsVpnUser("vpnuserlist", config)]}), - ("vm_dhcp_entry", {"process_iptables": False, "executor": [CsDhcp("dhcpentry", config)]}), - ("dhcp", {"process_iptables": False, "executor": [CsDhcp("dhcpentry", config)]}), - ("load_balancer", {"process_iptables": True, "executor": []}), - ("monitor_service", {"process_iptables": False, "executor": [CsMonitor("monitorservice", config)]}), - ("static_routes", {"process_iptables": False, "executor": [CsStaticRoutes("staticroutes", config)]}) + databag_map = OrderedDict([("guest_network", {"process_iptables": True, "executor": [CsVpcGuestNetwork("guestnetwork", config)]}), + ("ip_aliases", {"process_iptables": True, "executor": []}), + ("vm_password", {"process_iptables": False, "executor": [CsPassword("vmpassword", config)]}), + ("vm_metadata", {"process_iptables": False, "executor": [CsVmMetadata('vmdata', config)]}), + ("network_acl", {"process_iptables": True, "executor": []}), + ("firewall_rules", {"process_iptables": True, "executor": []}), + ("ipv6_firewall_rules", {"process_iptables": True, "executor": []}), + ("forwarding_rules", {"process_iptables": True, "executor": []}), + ("staticnat_rules", {"process_iptables": True, "executor": []}), + ("site_2_site_vpn", {"process_iptables": True, "executor": []}), + ("remote_access_vpn", {"process_iptables": True, "executor": []}), + ("vpn_user_list", {"process_iptables": False, "executor": [CsVpnUser("vpnuserlist", config)]}), + ("vm_dhcp_entry", {"process_iptables": False, "executor": [CsDhcp("dhcpentry", config)]}), + ("dhcp", {"process_iptables": False, "executor": [CsDhcp("dhcpentry", config)]}), + ("load_balancer", {"process_iptables": True, "executor": []}), + ("monitor_service", {"process_iptables": False, "executor": [CsMonitor("monitorservice", config)]}), + ("static_routes", {"process_iptables": False, "executor": [CsStaticRoutes("staticroutes", config)]}) ]) + if not config.is_vpc(): + databag_map.pop("guest_network") + def execDatabag(key, db): if key not in db.keys() or 'executor' not in db[key]: logging.warn("Unable to find config or executor(s) for the databag type %s" % key) @@ -1135,6 +1390,5 @@ def main(argv): red.set() return 0 - if __name__ == "__main__": main(sys.argv) diff --git a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py index afc1107a618..e676bb5aedd 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsAddress.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsAddress.py @@ -135,6 +135,15 @@ class CsInterface: def get_ip(self): return self.get_attr("public_ip") + def get_ip6(self): + if not self.config.is_vpc(): + return self.config.cmdline().get_dev_ip6prelen(self.get_device()) + if self.is_public(): + return self.config.guestnetwork().get_router_ip6prelen() + elif self.is_guest(): + return self.config.guestnetwork().get_dev_ip6prelen(self.get_device()) + return self.get_attr("public_ip6") + def get_network(self): return self.get_attr("network") @@ -147,6 +156,19 @@ class CsInterface: else: return self.config.cmdline().get_guest_gw() + def get_gateway6(self): + if self.config.is_vpc(): + if self.is_public(): + return self.config.guestnetwork().get_router_ip6gateway() + elif self.is_guest(): + return self.config.guestnetwork().get_dev_ip6gateway(self.get_device()) + else: + if self.is_public(): + return self.config.cmdline().get_ip6gateway() + elif self.is_guest(): + return self.config.cmdline().get_guest_ip6gateway() + return self.get_attr("gateway6") + def ip_in_subnet(self, ip): ipo = IPAddress(ip) net = IPNetwork("%s/%s" % (self.get_ip(), self.get_size())) @@ -155,10 +177,24 @@ class CsInterface: def get_gateway_cidr(self): return "%s/%s" % (self.get_gateway(), self.get_size()) + def get_gateway6_cidr(self): + gw6 = self.get_gateway6() + cidr6_size = self.get_cidr6_size() + if not gw6 or not cidr6_size or gw6 == "ERROR" or cidr6_size == "ERROR": + return False + return "%s/%s" % (self.get_gateway6(), self.get_cidr6_size()) + def get_size(self): """ Return the network size in bits (24, 16, 8 etc) """ return self.get_attr("size") + def get_cidr6_size(self): + if self.config.is_vpc() and self.is_guest(): + return self.config.guestnetwork().get_dev_ip6cidr(self.get_device()) + elif not self.config.is_vpc() and self.is_guest(): + return self.config.cmdline().get_guest_ip6cidr_size() + return self.get_attr("size6") + def get_device(self): return self.get_attr("device") @@ -328,6 +364,9 @@ class CsIP: if(self.cl.get_gateway()): route.add_defaultroute(self.cl.get_gateway()) + if self.config.is_router() and self.cl.get_ip6gateway(): + route.add_defaultroute_v6(self.cl.get_ip6gateway()) + def set_mark(self): cmd = "-A PREROUTING -i %s -m state --state NEW -j CONNMARK --set-xmark %s/0xffffffff" % \ (self.getDevice(), self.dnum) diff --git a/systemvm/debian/opt/cloud/bin/cs/CsConfig.py b/systemvm/debian/opt/cloud/bin/cs/CsConfig.py index 390f563c243..eaed71732d8 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsConfig.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsConfig.py @@ -16,7 +16,7 @@ # specific language governing permissions and limitations # under the License. -from CsDatabag import CsCmdLine +from CsDatabag import CsCmdLine, CsGuestNetwork from CsAddress import CsAddress import logging @@ -29,9 +29,12 @@ class CsConfig(object): __LOG_LEVEL = "INFO" __LOG_FORMAT = "%(asctime)s %(levelname)-8s %(message)s" cl = None + gn = None def __init__(self): self.fw = [] + self.ipv6_acl = [] + self.ipv6_fw = [] def set_address(self): self.ips = CsAddress("ips", self) @@ -42,15 +45,30 @@ class CsConfig(object): cls.cl = CsCmdLine("cmdline") return cls.cl + @classmethod + def get_guestnetwork_instance(cls): + if cls.gn is None: + cls.gn = CsGuestNetwork("guestnetwork") + return cls.gn + def cmdline(self): return self.get_cmdline_instance() + def guestnetwork(self): + return self.get_guestnetwork_instance() + def address(self): return self.ips def get_fw(self): return self.fw + def get_ipv6_acl(self): + return self.ipv6_acl + + def get_ipv6_fw(self): + return self.ipv6_fw + def get_logger(self): return self.__LOG_FILE diff --git a/systemvm/debian/opt/cloud/bin/cs/CsDatabag.py b/systemvm/debian/opt/cloud/bin/cs/CsDatabag.py index aa738dfe805..c000611af48 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsDatabag.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsDatabag.py @@ -65,6 +65,16 @@ class CsCmdLine(CsDataBag): return self.idata()['guestgw'] return False + def get_guest_ip6gateway(self): + if "guestgw6" in self.idata(): + return self.idata()['guestgw6'] + return False + + def get_guest_ip6cidr_size(self): + if "guestcidr6size" in self.idata(): + return self.idata()['guestcidr6size'] + return False + def is_redundant(self): if "redundant_router" in self.idata(): return self.idata()['redundant_router'] == "true" @@ -158,3 +168,74 @@ class CsCmdLine(CsDataBag): if 'advert_int' in self.idata(): return self.idata()['advert_int'] return 1 + + def get_ip6gateway(self): + if "ip6gateway" in self.idata(): + return self.idata()['ip6gateway'] + return False + + def get_dev_ip6prelen(self, devname): + ipkey = devname + "ip6" + prelenkey = devname + "ip6prelen" + if ipkey not in self.idata() or prelenkey not in self.idata(): + return False + return "%s/%s" % (self.idata()[ipkey], self.idata()[prelenkey]) + +class CsGuestNetwork(CsDataBag): + """ Get guestnetwork config parameters """ + + def get_dev_data(self, devname): + if devname in self.dbag and type(self.dbag[devname]) == list and len(self.dbag[devname]) > 0: + return self.dbag[devname][0] + return {} + + def get_dev_ip6gateway(self, devname): + nw = self.get_dev_data(devname) + gatewaykey = "router_guest_ip6_gateway" + if gatewaykey not in nw: + return False + return nw[gatewaykey] + + def get_dev_ip6cidr(self, devname): + nw = self.get_dev_data(devname) + cidrkey = "cidr6" + if cidrkey not in nw: + return False + return nw[cidrkey] + + def __get_device_router_ip6prelen(self, devname): + nw = self.get_dev_data(devname) + ip6key = "router_ip6" + ip6cidrkey = "router_ip6_cidr" + if ip6key not in nw or ip6cidrkey not in nw: + return False + ip6 = nw[ip6key] + ip6prelen = nw[ip6cidrkey].split("/")[1] + return "%s/%s" % (ip6, ip6prelen) + + def get_router_ip6prelen(self, devname=None): + if devname: + return self.__get_device_router_ip6prelen(devname) + else: + for key in self.dbag.keys(): + ip6prelen = self.__get_device_router_ip6prelen(key) + if ip6prelen: + return ip6prelen + return False + + def __get_device_router_ip6gateway(self, devname): + nw = self.get_dev_data(devname) + ip6gatewaykey = "router_ip6_gateway" + if ip6gatewaykey not in nw: + return False + return nw[ip6gatewaykey] + + def get_router_ip6gateway(self, devname=None): + if devname: + return self.__get_device_router_ip6gateway(devname) + else: + for key in self.dbag.keys(): + ip6gateway = self.__get_device_router_ip6gateway(key) + if ip6gateway: + return ip6gateway + return False diff --git a/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py b/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py index 01dfa7cac39..65bf4114a29 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py @@ -222,6 +222,49 @@ class CsNetfilters(object): The rule will not actually be removed on the host """ self.rules[:] = [x for x in self.rules if not x == rule] + def add_ip6_chain(self, address_family, table, chain, hook, action): + chain_policy = "" + if hook: + chain_policy = "type filter hook %s priority 0;" % hook + if chain_policy and action: + chain_policy = "%s policy %s;" % (chain_policy, action) + CsHelper.execute("nft add chain %s %s %s '{ %s }'" % (address_family, table, chain, chain_policy)) + if chain_policy: + CsHelper.execute("nft add rule %s %s %s icmpv6 type { echo-request, echo-reply, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept" % (address_family, table, chain)) + + def apply_ip6_rules(self, rules, type): + logging.debug("Add IPv6 rules: %s", rules) + if len(rules) == 0: + return + address_family = 'ip6' + table = 'ip6_firewall' + default_chains = [ + { "chain": "fw_chain_ingress", "hook": "input", "action": "drop"} + ] + if type == "acl": + table = 'ip6_acl' + default_chains = [ + { "chain": "acl_input", "hook": "input", "action": "drop" }, + { "chain": "acl_output", "hook": "output", "action": "accept" } + ] + CsHelper.execute("nft add table %s %s" % (address_family, table)) + for chain in default_chains: + self.add_ip6_chain(address_family, table, chain['chain'], chain['hook'], chain['action']) + for fw in rules: + chain = fw['chain'] + type = fw['type'] + rule = fw['rule'] + if type == "chain": + hook = "input" + if "egress" in chain: + hook = "output" + if chain.startswith("eth"): + hook = "" + self.add_ip6_chain(address_family, table, chain, hook, rule) + else: + logging.info("Add: rule=%s in address_family=%s table=%s, chain=%s", rule, address_family, table, chain) + CsHelper.execute("nft add rule %s %s %s %s" % (address_family, table, chain, rule)) + class CsNetfilter(object): diff --git a/systemvm/debian/opt/cloud/bin/cs/CsRedundant.py b/systemvm/debian/opt/cloud/bin/cs/CsRedundant.py index e0a7bfdf632..d6930803932 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsRedundant.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsRedundant.py @@ -247,6 +247,7 @@ class CsRedundant(object): CsHelper.service("ipsec", "stop") CsHelper.service("xl2tpd", "stop") CsHelper.service("dnsmasq", "stop") + CsHelper.service("radvd", "stop") interfaces = [interface for interface in self.address.get_interfaces() if interface.needs_vrrp()] for interface in interfaces: @@ -279,6 +280,8 @@ class CsRedundant(object): CsHelper.execute(cmd2) dev = interface.get_device() + self._remove_ipv6_guest_gateway() + CsHelper.service("conntrackd", "restart") CsHelper.service("ipsec", "stop") CsHelper.service("xl2tpd", "stop") @@ -326,9 +329,18 @@ class CsRedundant(object): route.add_defaultroute(gateway) except Exception: logging.error("ERROR getting gateway from device %s" % dev) + if dev == CsHelper.PUBLIC_INTERFACES[self.cl.get_type()]: + try: + self._add_ipv6_to_interface(interface, interface.get_ip6()) + if interface.get_gateway6(): + route.add_defaultroute_v6(interface.get_gateway6()) + except Exception as e: + logging.error("ERROR adding IPv6, getting IPv6 gateway from device %s: %s" % (dev, e)) else: logging.error("Device %s was not ready could not bring it up" % dev) + self._add_ipv6_guest_gateway() + logging.debug("Configuring static routes") static_routes = CsStaticRoutes("staticroutes", self.config) static_routes.process() @@ -413,3 +425,96 @@ class CsRedundant(object): str = " %s brd %s dev %s\n" % (interface.get_gateway_cidr(), interface.get_broadcast(), interface.get_device()) lines.append(str) return lines + + def _add_ipv6_to_interface(self, interface, ipv6): + """ + Add an IPv6 to an interface. This is useful for adding, + - guest IPv6 gateway for primary VR guest NIC + - public IPv6 for primary VR public NIC as its IPv6 gets lost on link down + """ + dev = '' + if dev == interface.get_device() or not ipv6 : + return + dev = interface.get_device() + command = "ip -6 address show %s | grep 'inet6 %s'" % (dev, ipv6) + ipConfigured = CsHelper.execute(command) + if ipConfigured: + logging.info("IPv6 address %s already present for %s" % (ipv6, dev)) + return + command = "ip link show %s | grep 'state UP'" % dev + devUp = CsHelper.execute(command) + if not devUp: + logging.error("ERROR setting IPv6 address for device %s as it is not ready" % dev) + return + logging.info("Device %s is present, let's add IPv6 address %s" % (dev, ipv6)) + cmd = "ip -6 addr add %s dev %s" % (ipv6, dev) + CsHelper.execute(cmd) + + def _remove_ipv6_to_interface(self, interface, ipv6): + """ + Remove an IPv6 to an interface. This is useful for removing, + - guest IPv6 gateway for primary VR guest NIC + """ + dev = '' + if dev == interface.get_device() or not ipv6 : + return + dev = interface.get_device() + command = "ip -6 address show %s | grep 'inet6 %s'" % (dev, ipv6) + ipConfigured = CsHelper.execute(command) + if ipConfigured: + command = "ip link show %s | grep 'state UP'" % dev + devUp = CsHelper.execute(command) + if not devUp: + logging.error("ERROR setting IPv6 address for device %s as it is not ready" % dev) + return + logging.info("Device %s is present, let's remove IPv6 address %s" % (dev, ipv6)) + cmd = "ip -6 addr delete %s dev %s" % (ipv6, dev) + CsHelper.execute(cmd) + else: + logging.info("IPv6 address %s not present for %s" % (ipv6, dev)) + return + + def _enable_radvd(self, dev): + """ + Setup radvd for primary VR + """ + if dev == '': + return + CsHelper.service("radvd", "enable") + CsHelper.execute("echo \"radvd\" >> /var/cache/cloud/enabled_svcs") + CsHelper.start_if_stopped("radvd") + + def _disable_radvd(self, dev): + """ + Disable radvd for non-primary VR + """ + if dev == '': + return + CsHelper.service("radvd", "stop") + CsHelper.service("radvd", "disable") + CsHelper.execute("sed -i \"s,radvd,,g\" /var/cache/cloud/enabled_svcs") + CsHelper.execute("sed -i '/^$/d' /var/cache/cloud/enabled_svcs") + logging.info(CsHelper.execute("systemctl status radvd")) + + + def _add_ipv6_guest_gateway(self): + """ + Configure guest network gateway as IPv6 address for guest interface + for redundant primary VR + """ + for interface in self.address.get_interfaces(): + if not interface.is_guest() or not interface.get_gateway6_cidr(): + continue + self._add_ipv6_to_interface(interface, interface.get_gateway6_cidr()) + self._enable_radvd(interface.get_device()) + + def _remove_ipv6_guest_gateway(self): + """ + Remove guest network gateway as IPv6 address for guest interface + for redundant backup VR + """ + for interface in self.address.get_interfaces(): + if not interface.is_guest() or not interface.get_gateway6_cidr(): + continue + self._remove_ipv6_to_interface(interface, interface.get_gateway6_cidr()) + self._disable_radvd(interface.get_device()) diff --git a/systemvm/debian/opt/cloud/bin/cs/CsRoute.py b/systemvm/debian/opt/cloud/bin/cs/CsRoute.py index a77a62572ad..d5df611df30 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsRoute.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsRoute.py @@ -127,3 +127,53 @@ class CsRoute: if rule in i.strip(): return True return False + + def set_route_v6(self, cmd, method="add"): + """ Add a IPv6 route if it is not already defined """ + found = False + search = cmd + for i in CsHelper.execute("ip -6 route show " + search): + found = True + if not found and method == "add": + logging.info("Add " + cmd) + cmd = "ip -6 route add " + cmd + elif found and method == "delete": + logging.info("Delete " + cmd) + cmd = "ip -6 route delete " + cmd + else: + return + CsHelper.execute(cmd) + + def add_defaultroute_v6(self, gateway): + """ Add a default route + # for example, ip -6 route add default via fd80:20:20:20::1 + :param str gateway + :return: bool + """ + if not gateway: + raise Exception("Gateway cannot be None.") + + logging.info("Checking if default ipv6 route is present") + route_found = CsHelper.execute("ip -6 route list default") + if len(route_found) > 0: + logging.info("Default IPv6 route found: " + route_found[0]) + return False + else: + cmd = "default via " + gateway + logging.info("Adding default IPv6 route") + self.set_route_v6(cmd) + return True + + def add_network_route_v6(self, dev, address): + """ Wrapper method that adds table name and device to route statement """ + # ip -6 route add dev eth1 2021:10:10:10::1/64 + logging.info("Adding IPv6 route: dev " + dev + " network: " + address + " if not present") + cmd = "%s dev %s proto kernel" % (address, dev) + self.set_route_v6(cmd) + + def delete_network_route_v6(self, dev, address): + """ Wrapper method that deletes table name and device from route statement """ + # ip -6 route del dev eth1 2021:10:10:10::1/64 + logging.info("Deleting IPv6 route: dev " + dev + " network: " + address + " if present") + cmd = "%s dev %s" % (address, dev) + self.set_route_v6(cmd, method="delete") diff --git a/systemvm/debian/opt/cloud/bin/cs/CsVpcGuestNetwork.py b/systemvm/debian/opt/cloud/bin/cs/CsVpcGuestNetwork.py new file mode 100755 index 00000000000..e80f16e915a --- /dev/null +++ b/systemvm/debian/opt/cloud/bin/cs/CsVpcGuestNetwork.py @@ -0,0 +1,116 @@ +# 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. +import logging +import os.path +from cs.CsDatabag import CsDataBag +from CsFile import CsFile +import CsHelper + +VPC_PUBLIC_INTERFACE = "eth1" + +RADVD_CONF = "/etc/radvd.conf" +RADVD_CONF_NEW = "/etc/radvd.conf.new" + +class CsVpcGuestNetwork(CsDataBag): + """ Manage Vpc Guest Networks """ + + def process(self): + logging.debug("Processing CsVpcGuestNetwork") + self.conf = CsFile(RADVD_CONF_NEW) + self.conf.empty() + for item in self.dbag: + if item == "id": + continue + for address in self.dbag[item]: + if address['add']: + self.add_address_route(address) + self.add_radvd_conf(address) + else: + self.remove_address_route(address) + self.conf.commit() + file = CsFile(RADVD_CONF) + if not file.compare(self.conf): + CsHelper.copy(RADVD_CONF_NEW, RADVD_CONF) + logging.debug("CsVpcGuestNetwork:: will restart radvd !") + CsHelper.service("radvd", "restart") + + def __disable_dad(self, device): + CsHelper.execute("sysctl net.ipv6.conf." + device + ".accept_dad=0") + CsHelper.execute("sysctl net.ipv6.conf." + device + ".use_tempaddr=0") + + def add_address_route(self, entry): + if 'router_guest_ip6' in entry.keys() and entry['router_guest_ip6']: + self.enable_ipv6(entry['device']) + cidr_size = entry['router_guest_ip6_cidr'].split("/")[-1] + full_addr = entry['router_guest_ip6_gateway'] + "/" + cidr_size + if not CsHelper.execute("ip -6 addr show dev %s | grep -w %s" % (entry['device'], full_addr)): + CsHelper.execute("ip -6 addr add %s dev %s" % (full_addr, entry['device'])) + if 'router_ip6' in entry.keys() and entry['router_ip6']: + self.__disable_dad(VPC_PUBLIC_INTERFACE) + full_public_addr = entry['router_ip6'] + "/" + cidr_size + if not CsHelper.execute("ip -6 addr show dev %s | grep -w %s" % (VPC_PUBLIC_INTERFACE, full_public_addr)): + CsHelper.execute("ip -6 addr add %s dev %s" % (full_public_addr, VPC_PUBLIC_INTERFACE)) + if not CsHelper.execute("ip -6 route list default via %s" % entry['router_ip6_gateway']): + CsHelper.execute("ip -6 route add default via %s" % entry['router_ip6_gateway']) + else: + return + + def remove_address_route(self, entry): + if 'router_guest_ip6' in entry.keys() and entry['router_guest_ip6']: + cidr_size = entry['router_guest_ip6_cidr'].split("/")[-1] + full_addr = entry['router_guest_ip6_gateway'] + "/" + cidr_size + CsHelper.execute("ip -6 addr del %s dev %s" % (full_addr, entry['device'])) + if 'router_ip6' in entry.keys() and entry['router_ip6']: + full_public_addr = entry['router_ip6'] + "/" + cidr_size + CsHelper.execute("ip -6 addr del %s dev %s" % (full_public_addr, VPC_PUBLIC_INTERFACE)) + else: + return + + def enable_ipv6(self, device): + logging.debug("Enabling IPv6 in this router") + CsHelper.execute("sysctl net.ipv6.conf.all.disable_ipv6=0") + CsHelper.execute("sysctl net.ipv6.conf.all.forwarding=1") + CsHelper.execute("sysctl net.ipv6.conf.all.accept_ra=1") + + # to solve the 'tentative dadfailed' when perform rolling upgrade + CsHelper.execute("sysctl net.ipv6.conf.all.accept_dad=0") + CsHelper.execute("sysctl net.ipv6.conf.default.accept_dad=0") + CsHelper.execute("sysctl net.ipv6.conf.all.use_tempaddr=0") + CsHelper.execute("sysctl net.ipv6.conf.default.use_tempaddr=0") + self.__disable_dad(device) + + def add_radvd_conf(self, entry): + if 'router_guest_ip6' in entry.keys() and entry['router_guest_ip6']: + cidr_size = entry['router_guest_ip6_cidr'].split("/")[-1] + full_addr = entry['router_guest_ip6_gateway'] + "/" + cidr_size + self.conf.append("interface %s" % entry['device']) + self.conf.append("{") + self.conf.append(" AdvSendAdvert on;") + self.conf.append(" MinRtrAdvInterval 5;") + self.conf.append(" MaxRtrAdvInterval 15;") + self.conf.append(" prefix %s" % full_addr) + self.conf.append(" {") + self.conf.append(" AdvOnLink on;") + self.conf.append(" AdvAutonomous on;") + self.conf.append(" };") + if 'dns6' in entry.keys() and entry['dns6']: + for dns in entry['dns6'].split(","): + self.conf.append(" RDNSS %s" % dns) + self.conf.append(" {") + self.conf.append(" AdvRDNSSLifetime 30;") + self.conf.append(" };") + self.conf.append("};") diff --git a/systemvm/debian/opt/cloud/bin/diagnostics.py b/systemvm/debian/opt/cloud/bin/diagnostics.py index 477f99d9d3f..737b12206db 100755 --- a/systemvm/debian/opt/cloud/bin/diagnostics.py +++ b/systemvm/debian/opt/cloud/bin/diagnostics.py @@ -50,12 +50,24 @@ def get_command(): else: return cmd + " -c 4" + elif cmd_type == 'ping6': + if '-c' in arguments: + return cmd + else: + return cmd + " -c 4" + elif cmd_type == 'traceroute': if '-m' in arguments: return cmd else: return cmd + " -m 20" + elif cmd_type == 'traceroute6': + if '-m' in arguments: + return cmd + else: + return cmd + " -m 20" + elif cmd_type == 'arping': if '-c' in arguments: return cmd diff --git a/systemvm/debian/opt/cloud/bin/merge.py b/systemvm/debian/opt/cloud/bin/merge.py index 4ab9911824d..caa3c6bff84 100755 --- a/systemvm/debian/opt/cloud/bin/merge.py +++ b/systemvm/debian/opt/cloud/bin/merge.py @@ -112,6 +112,8 @@ class updateDataBag: dbag = self.process_network_acl(self.db.getDataBag()) elif self.qFile.type == 'firewallrules': dbag = self.process_firewallrules(self.db.getDataBag()) + elif self.qFile.type == 'ipv6firewallrules': + dbag = self.process_ipv6firewallrules(self.db.getDataBag()) elif self.qFile.type == 'loadbalancer': dbag = self.process_loadbalancer(self.db.getDataBag()) elif self.qFile.type == 'monitorservice': @@ -177,6 +179,9 @@ class updateDataBag: def process_firewallrules(self, dbag): return cs_firewallrules.merge(dbag, self.qFile.data) + def process_ipv6firewallrules(self, dbag): + return cs_firewallrules.merge(dbag, self.qFile.data) + def process_loadbalancer(self, dbag): return cs_loadbalancer.merge(dbag, self.qFile.data) diff --git a/systemvm/debian/opt/cloud/bin/setup/bootstrap.sh b/systemvm/debian/opt/cloud/bin/setup/bootstrap.sh index 4720237543f..b3409213ed0 100755 --- a/systemvm/debian/opt/cloud/bin/setup/bootstrap.sh +++ b/systemvm/debian/opt/cloud/bin/setup/bootstrap.sh @@ -85,6 +85,15 @@ config_sysctl() { sed -i "/^vm.vfs_cache_pressure/ c\vm.vfs_cache_pressure = 100" /etc/sysctl.conf fi + eth0_ip6=$(grep -Po 'eth0ip6=\K[0-9a-zA-Z:]*' $CMDLINE) + eth2_ip6=$(grep -Po 'eth2ip6=\K[0-9a-zA-Z:]*' $CMDLINE) + if [ -n "$eth0_ip6" ] || [ -n "$eth2_ip6" ] + then + sed -i "s/net.ipv6.conf.all.disable_ipv6 =.*$/net.ipv6.conf.all.disable_ipv6 = 0/" /etc/sysctl.conf + sed -i "s/net.ipv6.conf.all.forwarding =.*$/net.ipv6.conf.all.forwarding = 1/" /etc/sysctl.conf + sed -i "s/net.ipv6.conf.all.accept_ra =.*$/net.ipv6.conf.all.accept_ra = 1/" /etc/sysctl.conf + fi + sync sysctl -p } diff --git a/systemvm/debian/opt/cloud/bin/setup/common.sh b/systemvm/debian/opt/cloud/bin/setup/common.sh index cad8af8c015..b937fd889bb 100755 --- a/systemvm/debian/opt/cloud/bin/setup/common.sh +++ b/systemvm/debian/opt/cloud/bin/setup/common.sh @@ -124,6 +124,9 @@ setup_interface_ipv6() { local prelen="$3" local intf=eth${intfnum} + sysctl net.ipv6.conf.$intf.accept_dad=0 + sysctl net.ipv6.conf.$intf.use_tempaddr=0 + echo "iface $intf inet6 static" >> /etc/network/interfaces echo " address $ipv6 " >> /etc/network/interfaces echo " netmask $prelen" >> /etc/network/interfaces @@ -272,12 +275,22 @@ setup_common() { if [ -n "$ETH0_IP6" ] then setup_interface_ipv6 "0" $ETH0_IP6 $ETH0_IP6_PRELEN + rm -rf /etc/radvd.conf + setup_radvd "0" $ETH0_IP6 $ETH0_IP6_PRELEN true + elif [ -n "$GUEST_GW6" -a -n "$GUEST_CIDR6_SIZE" ] + then + rm -rf /etc/radvd.conf + setup_radvd "0" $GUEST_GW6 $GUEST_CIDR6_SIZE false fi setup_interface "1" $ETH1_IP $ETH1_MASK $GW if [ -n "$ETH2_IP" ] then setup_interface "2" $ETH2_IP $ETH2_MASK $GW fi + if [ -n "$ETH2_IP6" ] + then + setup_interface_ipv6 "2" $ETH2_IP6 $ETH2_IP6_PRELEN + fi echo $NAME > /etc/hostname echo 'AVAHI_DAEMON_DETECT_LOCAL=0' > /etc/default/avahi-daemon @@ -357,6 +370,35 @@ setup_common() { fi } +setup_radvd() { + log_it "Setting up radvd" + + local intfnum=$1 + local ipv6="$2" + local prelen="$3" + local enable="$4" + + local intf=eth${intfnum} + local ip6cidr="$ipv6/$prelen" + + cp /etc/radvd.conf.tmpl /etc/radvd.conf.$intf + sed -i "s,{{ GUEST_INTERFACE }},$intf,g" /etc/radvd.conf.$intf + sed -i "s,{{ IPV6_CIDR }},$ip6cidr,g" /etc/radvd.conf.$intf + RDNSS_CFG= + if [ -n "$IP6_NS1" ];then + RDNSS_CFG=$RDNSS_CFG" RDNSS $IP6_NS1\n {\n AdvRDNSSLifetime 30;\n };\n" + fi + if [ -n "$IP6_NS2" ];then + RDNSS_CFG=$RDNSS_CFG" RDNSS $IP6_NS2\n {\n AdvRDNSSLifetime 30;\n };\n" + fi + sed -i "s,{{ RDNSS_CONFIG }},$RDNSS_CFG,g" /etc/radvd.conf.$intf + cat /etc/radvd.conf.$intf >> /etc/radvd.conf + if [ "$enable" = true ] ; then + systemctl enable radvd + echo "radvd" >> /var/cache/cloud/enabled_svcs + fi +} + setup_dnsmasq() { log_it "Setting up dnsmasq" @@ -659,6 +701,12 @@ parse_cmd_line() { eth0ip6prelen) export ETH0_IP6_PRELEN=$VALUE ;; + eth2ip6) + export ETH2_IP6=$VALUE + ;; + eth2ip6prelen) + export ETH2_IP6_PRELEN=$VALUE + ;; internaldns1) export internalNS1=$VALUE ;; @@ -677,6 +725,9 @@ parse_cmd_line() { ip6dns2) export IP6_NS2=$VALUE ;; + ip6firewall) + export IP6_FIREWALL=$VALUE + ;; domain) export DOMAIN=$VALUE ;; @@ -728,6 +779,12 @@ parse_cmd_line() { guestcidrsize) export GUEST_CIDR_SIZE=$VALUE ;; + guestgw6) + export GUEST_GW6=$VALUE + ;; + guestcidr6size) + export GUEST_CIDR6_SIZE=$VALUE + ;; router_pr) export ROUTER_PR=$VALUE ;; diff --git a/test/integration/smoke/test_network_ipv6.py b/test/integration/smoke/test_network_ipv6.py new file mode 100644 index 00000000000..b028b8b7aa3 --- /dev/null +++ b/test/integration/smoke/test_network_ipv6.py @@ -0,0 +1,1255 @@ +# 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. +""" BVT tests for Network offerings""" + +#Import Local Modules +from marvin.codes import FAILED +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import (createGuestNetworkIpv6Prefix, + listGuestNetworkIpv6Prefixes, + deleteGuestNetworkIpv6Prefix, + listIpv6FirewallRules, + createIpv6FirewallRule, + deleteIpv6FirewallRule) +from marvin.lib.utils import (isAlmostEqual, + cleanup_resources, + random_gen, + get_process_status, + get_host_credentials) +from marvin.lib.base import (Configurations, + Domain, + NetworkOffering, + VpcOffering, + Account, + PublicIpRange, + Network, + Router, + ServiceOffering, + VirtualMachine, + NIC, + Host) +from marvin.lib.common import (get_domain, + get_zone, + list_hosts, + get_test_template, + get_template) +from marvin.sshClient import SshClient +from marvin.cloudstackException import CloudstackAPIException +from marvin.lib.decoratorGenerators import skipTestIf + +from nose.plugins.attrib import attr +from ipaddress import IPv6Network +from random import getrandbits, choice, randint +import time +import logging + +ipv6_offering_config_name = "ipv6.offering.enabled" +ULA_BASE = IPv6Network("fd00::/8") +PREFIX_OPTIONS = [i for i in range(48, 65, 4)] +FIREWALL_TABLE = "ip6_firewall" +FIREWALL_CHAINS = { + "Ingress": "fw_chain_ingress", + "Egress": "fw_chain_egress" +} +CIDR_IPV6_ANY = "::/0" +ICMPV6_TYPE = { + 1: "destination-unreachable", + 2: "packet-too-big", + 3: "time-exceeded", + 4: "parameter-problem", + 128: "echo-request", + 129: "echo-reply", + 130: "mld-listener-query", + 131: "mld-listener-report", + 132: "mld-listener-done", + 132: "mld-listener-reduction", + 133: "nd-router-solicit", + 134: "nd-router-advert", + 135: "nd-neighbor-solicit", + 136: "nd-neighbor-advert", + 137: "nd-redirect", + 138: "router-renumbering", + 141: "ind-neighbor-solicit", + 142: "ind-neighbor-advert", + 143: "mld2-listener-report" +} +ICMPV6_CODE_TYPE = { + 0: "no-route", + 1: "admin-prohibited", + 3: "addr-unreachable", + 4: "port-unreachable", + 5: "policy-fail", + 6: "reject-route" +} +ICMPV6_TYPE_ANY = "{ destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-done, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, router-renumbering }" +TCP_UDP_PORT_ANY = "{ 0-65535 }" + +class TestCreateIpv6NetworkVpcOffering(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestCreateIpv6NetworkVpcOffering, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.initial_ipv6_offering_enabled = Configurations.list( + cls.apiclient, + name=ipv6_offering_config_name)[0].value + cls._cleanup = [] + return + + @classmethod + def tearDownClass(cls): + if cls.initial_ipv6_offering_enabled != None: + Configurations.update(cls.apiclient, + ipv6_offering_config_name, + cls.initial_ipv6_offering_enabled) + super(TestCreateIpv6NetworkVpcOffering, cls).tearDownClass() + + def setUp(self): + self.services = self.testClient.getParsedTestDataConfig() + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created templates + cleanup_resources(self.apiclient, self.cleanup) + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_01_create_ipv6_network_offering(self): + """Test to create network offering + + # Validate the following: + # 1. createNetworkOffering should return valid info for new offering + # 2. The Cloud Database contains the valid information + """ + Configurations.update(self.apiclient, + ipv6_offering_config_name, + "true") + ipv6_service = self.services["network_offering"] + ipv6_service["internetprotocol"] = "dualstack" + network_offering = NetworkOffering.create( + self.apiclient, + ipv6_service + ) + self.cleanup.append(network_offering) + + self.debug("Created Network offering with ID: %s" % network_offering.id) + + list_network_off_response = NetworkOffering.list(self.apiclient, + id=network_offering.id) + self.assertEqual( + isinstance(list_network_off_response, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(list_network_off_response), + 0, + "Check Network offering is created" + ) + network_off_response = list_network_off_response[0] + + self.assertEqual( + network_off_response.id, + network_offering.id, + "Check server id in listNetworkOfferings" + ) + self.assertEqual( + network_off_response.internetprotocol.lower(), + ipv6_service["internetprotocol"].lower(), + "Check internetprotocol in listNetworkOfferings" + ) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_02_create_ipv6_network_offering_fail(self): + """Test to create network offering + + # Validate the following: + # 1. createNetworkOffering should fail + """ + Configurations.update(self.apiclient, + ipv6_offering_config_name, + "false") + ipv6_service = self.services["network_offering"] + ipv6_service["internetprotocol"] = "dualstack" + try: + network_offering = NetworkOffering.create( + self.apiclient, + ipv6_service + ) + self.cleanup.append(network_offering) + self.fail("Network offering created despite global setting - %s set to false" % ipv6_offering_config_name) + except CloudstackAPIException as e: + self.debug("Network offering creation failed as expected %s " % e) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_03_create_ipv6_vpc_offering(self): + """Test to create network offering + + # Validate the following: + # 1. createVpcOffering should return valid info for new offering + # 2. The Cloud Database contains the valid information + """ + Configurations.update(self.apiclient, + ipv6_offering_config_name, + "true") + ipv6_service = self.services["vpc_offering"] + ipv6_service["internetprotocol"] = "dualstack" + vpc_offering = VpcOffering.create( + self.apiclient, + ipv6_service + ) + self.cleanup.append(vpc_offering) + + self.debug("Created VPC offering with ID: %s" % vpc_offering.id) + + list_vpc_off_response = VpcOffering.list(self.apiclient, + id=vpc_offering.id) + self.assertEqual( + isinstance(list_vpc_off_response, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(list_vpc_off_response), + 0, + "Check VPC offering is created" + ) + vpc_off_response = list_vpc_off_response[0] + self.assertEqual( + vpc_off_response.id, + vpc_offering.id, + "Check server id in listVpcOfferings" + ) + self.assertEqual( + vpc_off_response.internetprotocol.lower(), + ipv6_service["internetprotocol"].lower(), + "Check internetprotocol in listVpcOfferings" + ) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_04_create_ipv6_vpc_offering_fail(self): + """Test to create VPC offering failure + + # Validate the following: + # 1. createVpcOffering should fail + """ + Configurations.update(self.apiclient, + ipv6_offering_config_name, + "false") + ipv6_service = self.services["vpc_offering"] + ipv6_service["internetprotocol"] = "dualstack" + try: + vpc_offering = VpcOffering.create( + self.apiclient, + ipv6_service + ) + self.cleanup.append(vpc_offering) + self.fail("VPC offering created despite global setting - %s set to false" % ipv6_offering_config_name) + except CloudstackAPIException as e: + self.debug("VPC offering creation failed as expected %s " % e) + return + +class TestIpv6PublicIpRange(cloudstackTestCase): + + def setUp(self): + self.services = self.testClient.getParsedTestDataConfig() + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created templates + cleanup_resources(self.apiclient, self.cleanup) + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @classmethod + def setUpClass(cls): + testClient = super(TestIpv6PublicIpRange, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls._cleanup = [] + return + + @classmethod + def tearDownClass(cls): + super(TestIpv6PublicIpRange, cls).tearDownClass() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_01_create_ipv6_public_ip_range(self): + """Test to add IPv6 public IP range + + # Validate the following: + # 1. createVlanIpRange should return valid info for new public range + # 2. The Cloud Database contains the valid information + """ + ipv6_publiciprange_service = self.services["publicip6range"] + ipv6_publiciprange_service["zoneid"] = self.zone.id + ipv6_publiciprange = PublicIpRange.create( + self.apiclient, + ipv6_publiciprange_service + ) + self.cleanup.append(ipv6_publiciprange) + + self.debug("Created IPv6 public IP range with ID: %s" % ipv6_publiciprange.vlan.id) + ipv6_publiciprange = ipv6_publiciprange.vlan + + public_ip_ranges = PublicIpRange.list( + self.apiclient, + id=ipv6_publiciprange.id + ) + self.assertEqual( + isinstance(public_ip_ranges, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(public_ip_ranges), + 0, + "Check public IP range is created" + ) + public_ip_range = public_ip_ranges[0] + + self.assertEqual( + public_ip_range.id, + ipv6_publiciprange.id, + "Check server id" + ) + self.assertEqual( + public_ip_range.ip6cidr, + ipv6_publiciprange_service["ip6cidr"], + "Check ip6cidr for IPv6 public IP range" + ) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_02_create_ipv6_public_ip_range_fail(self): + """Test to add IPv6 public IP range failure + + # Validate the following: + # 1. createVlanIpRange should return valid info for new public range + # 2. The Cloud Database contains the valid information + """ + ipv6_publiciprange_service = self.services["publicip6range"] + cidr = ipv6_publiciprange_service["ip6cidr"] + x = cidr.split("/") + x[1] = "72" + cidr = "/".join(x) + ipv6_publiciprange_service["ip6cidr"] = cidr + ipv6_publiciprange_service["zoneid"] = self.zone.id + try: + ipv6_publiciprange = PublicIpRange.create( + self.apiclient, + ipv6_publiciprange_service + ) + except Exception as e: + self.debug("IPv6 public range creation failed as expected %s " % e) + ipv6_publiciprange = None + if ipv6_publiciprange != None: + self.debug("Created IPv6 public range with ID: %s. Deleting it before failure" % ipv6_publiciprange.id) + self.cleanup.append(ipv6_publiciprange) + self.fail("IPv6 guest prefix created despite CIDR size greater than 64") + return + +class TestIpv6GuestPrefix(cloudstackTestCase): + + def setUp(self): + self.services = self.testClient.getParsedTestDataConfig() + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created templates + cleanup_resources(self.apiclient, self.cleanup) + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @classmethod + def setUpClass(cls): + testClient = super(TestIpv6GuestPrefix, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls._cleanup = [] + return + + @classmethod + def tearDownClass(cls): + super(TestIpv6GuestPrefix, cls).tearDownClass() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_01_create_ipv6_guest_prefix(self): + """Test to add IPv6 guest prefix + + # Validate the following: + # 1. createGuestNetworkIpv6Prefix should return valid info for new IPv6 prefix + # 2. The Cloud Database contains the valid information + """ + ipv6_guestprefix_service = self.services["guestip6prefix"] + cmd = createGuestNetworkIpv6Prefix.createGuestNetworkIpv6PrefixCmd() + cmd.zoneid = self.zone.id + cmd.prefix = ipv6_guestprefix_service["prefix"] + ipv6_guestprefix = self.apiclient.createGuestNetworkIpv6Prefix(cmd) + + self.debug("Created IPv6 guest prefix with ID: %s" % ipv6_guestprefix.id) + + cmd = listGuestNetworkIpv6Prefixes.listGuestNetworkIpv6PrefixesCmd() + cmd.id = ipv6_guestprefix.id + ipv6_guestprefixes = self.apiclient.listGuestNetworkIpv6Prefixes(cmd) + self.assertEqual( + isinstance(ipv6_guestprefixes, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(ipv6_guestprefixes), + 0, + "Check guest IPv6 prefix is created" + ) + ipv6_guestprefix_response = ipv6_guestprefixes[0] + + self.assertEqual( + ipv6_guestprefix.id, + ipv6_guestprefix_response.id, + "Check server id" + ) + self.assertEqual( + ipv6_guestprefix_response.prefix, + ipv6_guestprefix_service["prefix"], + "Check prefix for IPv6" + ) + + cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() + cmd.id = ipv6_guestprefix.id + self.apiclient.deleteGuestNetworkIpv6Prefix(cmd) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_02_create_ipv6_guest_prefix_fail(self): + """Test to add IPv6 guest prefix failure + + # Validate the following: + # 1. createGuestNetworkIpv6Prefix should fail + """ + ipv6_guestprefix_service = self.services["guestip6prefix"] + cmd = createGuestNetworkIpv6Prefix.createGuestNetworkIpv6PrefixCmd() + cmd.zoneid = self.zone.id + prefix = ipv6_guestprefix_service["prefix"] + x = prefix.split("/") + x[1] = "72" + prefix = "/".join(x) + cmd.prefix = prefix + try: + ipv6_guestprefix = self.apiclient.createGuestNetworkIpv6Prefix(cmd) + except Exception as e: + self.debug("IPv6 guest prefix creation failed as expected %s " % e) + ipv6_guestprefix = None + if ipv6_guestprefix != None: + self.debug("Created IPv6 guest prefix with ID: %s. Deleting it before failure" % ipv6_guestprefix.id) + cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() + cmd.id = ipv6_guestprefix.id + self.apiclient.deleteGuestNetworkIpv6Prefix(cmd) + self.fail("IPv6 guest prefix created despite CIDR size greater than 64") + return + +class TestIpv6Network(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestIpv6Network, cls).getClsTestClient() + cls.services = testClient.getParsedTestDataConfig() + cls.apiclient = testClient.getApiClient() + cls.dbclient = testClient.getDbConnection() + cls.test_ipv6_guestprefix = None + cls.initial_ipv6_offering_enabled = None + cls._cleanup = [] + cls.routerDetailsMap = {} + + cls.logger = logging.getLogger('TestIpv6Network') + + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls.ipv6NotSupported = False + + ipv6_guestprefix = cls.getGuestIpv6Prefix() + if ipv6_guestprefix == None: + cls.ipv6NotSupported = True + if cls.ipv6NotSupported == False: + ipv6_publiciprange = cls.getPublicIpv6Range() + if ipv6_publiciprange == None: + cls.ipv6NotSupported = True + + if cls.ipv6NotSupported == False: + cls.initial_ipv6_offering_enabled = Configurations.list( + cls.apiclient, + name=ipv6_offering_config_name)[0].value + Configurations.update(cls.apiclient, + ipv6_offering_config_name, + "true") + cls.domain = get_domain(cls.apiclient) + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + cls.hypervisor = testClient.getHypervisorInfo() + cls.template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"] + ) + else: + cls.debug("IPv6 is not supported, skipping tests!") + return + + @classmethod + def tearDownClass(cls): + if cls.initial_ipv6_offering_enabled != None: + Configurations.update(cls.apiclient, + ipv6_offering_config_name, + cls.initial_ipv6_offering_enabled) + super(TestIpv6Network, cls).tearDownClass() + if cls.test_ipv6_guestprefix != None: + cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() + cmd.id = cls.test_ipv6_guestprefix.id + cls.apiclient.deleteGuestNetworkIpv6Prefix(cmd) + + @classmethod + def getGuestIpv6Prefix(cls): + cmd = listGuestNetworkIpv6Prefixes.listGuestNetworkIpv6PrefixesCmd() + cmd.zoneid = cls.zone.id + ipv6_prefixes_response = cls.apiclient.listGuestNetworkIpv6Prefixes(cmd) + if isinstance(ipv6_prefixes_response, list) == True and len(ipv6_prefixes_response) > 0: + return ipv6_prefixes_response[0] + ipv6_guestprefix_service = cls.services["guestip6prefix"] + cmd = createGuestNetworkIpv6Prefix.createGuestNetworkIpv6PrefixCmd() + cmd.zoneid = cls.zone.id + cmd.prefix = ipv6_guestprefix_service["prefix"] + ipv6_guestprefix = cls.apiclient.createGuestNetworkIpv6Prefix(cmd) + cls.test_ipv6_guestprefix = ipv6_guestprefix + return ipv6_guestprefix + + @classmethod + def getPublicIpv6Range(cls): + list_public_ip_range_response = PublicIpRange.list( + cls.apiclient, + zoneid=cls.zone.id + ) + ipv4_range_vlan = None + if isinstance(list_public_ip_range_response, list) == True and len(list_public_ip_range_response) > 0: + for ip_range in list_public_ip_range_response: + if ip_range.ip6cidr != None and ip_range.ip6gateway != None: + return ip_range + if ip_range.netmask != None and ip_range.gateway != None: + vlan = ip_range.vlan + if ipv4_range_vlan == None and vlan.startswith("vlan://"): + vlan = vlan.replace("vlan://", "") + ipv4_range_vlan = int(vlan) + ipv6_publiciprange_service = cls.services["publicip6range"] + ipv6_publiciprange_service["zoneid"] = cls.zone.id + ipv6_publiciprange_service["vlan"] = ipv4_range_vlan + ipv6_publiciprange = PublicIpRange.create( + cls.apiclient, + ipv6_publiciprange_service + ) + cls._cleanup.append(ipv6_publiciprange) + return ipv6_publiciprange + + def setUp(self): + self.services = self.testClient.getParsedTestDataConfig() + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created templates + cleanup_resources(self.apiclient, reversed(self.cleanup)) + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def getRandomIpv6Cidr(self): + prefix_length = choice(PREFIX_OPTIONS) + random_suffix = getrandbits(40) << (128-prefix_length) + base_address = ULA_BASE.network_address + random_suffix + return str(IPv6Network((base_address, prefix_length))) + + def createTinyServiceOffering(self): + self.service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offerings"]["big"], + ) + self.cleanup.append(self.service_offering) + + def createIpv6NetworkOfferingInternal(self, is_redundant, egressdefaultpolicy=True): + ipv6_service = self.services["network_offering"] + if is_redundant: + ipv6_service = self.services["nw_off_isolated_RVR"] + ipv6_service["internetprotocol"] = "dualstack" + if egressdefaultpolicy: + ipv6_service["egress_policy"] = egressdefaultpolicy + network_offering = NetworkOffering.create( + self.apiclient, + ipv6_service + ) + network_offering.update(self.apiclient, state='Enabled') + return network_offering + + def createIpv6NetworkOffering(self, is_redundant): + self.network_offering = self.createIpv6NetworkOfferingInternal(is_redundant, False) + self.cleanup.append(self.network_offering) + + def createIpv6NetworkOfferingForUpdate(self, is_redundant): + self.network_offering_update = self.createIpv6NetworkOfferingInternal(is_redundant) + self.cleanup.append(self.network_offering_update) + + + def deployIpv6Network(self): + self.services["network"]["networkoffering"] = self.network_offering.id + self.network = Network.create( + self.apiclient, + self.services["network"], + self.account.name, + self.account.domainid, + zoneid=self.zone.id + ) + self.cleanup.append(self.network) + + def deployIpv6NetworkVm(self): + if self.template == FAILED: + assert False, "get_test_template() failed to return template" + self.services["virtual_machine"]["zoneid"] = self.zone.id + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + networkids=self.network.id, + serviceofferingid=self.service_offering.id + ) + self.cleanup.append(self.virtual_machine) + + def checkIpv6NetworkBasic(self): + self.debug("Listing network: %s" % (self.network.name)) + ipv6_network = Network.list(self.apiclient,listall="true",id=self.network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network, + None, + "User is not able to retrieve network details %s" % self.network.id) + self.assertNotEqual(ipv6_network.ip6cidr, + None, + "IPv6 CIDR for network is empty") + self.assertNotEqual(ipv6_network.ip6gateway, + None, + "IPv6 gateway for network is empty") + self.assertNotEqual(ipv6_network.ip6routes, + None, + "IPv6 routes for network is empty") + self.network_ipv6_routes = ipv6_network.ip6routes + + def checkIpv6NetworkRoutersBasic(self): + self.debug("Listing routers for network: %s" % self.network.name) + self.routers = Router.list( + self.apiclient, + networkid=self.network.id, + listall=True + ) + self.assertTrue( + isinstance(self.routers, list), + "Check listRouters response returns a valid list" + ) + self.assertTrue( + len(self.routers) > 0, + "Router for the network isn't found" + ) + for router in self.routers: + self.assertFalse( + router.isredundantrouter == True and router.redundantstate == "FAULT", + "Router for the network is in FAULT state" + ) + nics = router.nic + for nic in nics: + if (nic.traffictype == 'Guest' and router.isredundantrouter == False) or nic.traffictype == 'Public': + self.assertNotEqual(nic.ip6address, + None, + "IPv6 address for router %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6cidr, + None, + "IPv6 CIDR for router %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6gateway, + None, + "IPv6 gateway for router %s NIC is empty" % nic.traffictype) + + + def getRouterProcessStatus(self, router, cmd): + if router.id not in self.routerDetailsMap or self.routerDetailsMap[router.id] is None: + connect_ip = self.apiclient.connection.mgtSvr + connect_user = self.apiclient.connection.user + connect_passwd = self.apiclient.connection.passwd + hypervisor = self.hypervisor + if self.hypervisor.lower() not in ('vmware', 'hyperv'): + hosts = Host.list( + self.apiclient, + zoneid=router.zoneid, + type='Routing', + state='Up', + id=router.hostid + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check list host returns a valid list" + ) + host = hosts[0] + connect_ip = host.ipaddress + hypervisor = None + try: + connect_user, connect_passwd= get_host_credentials( + self.config, host.ipaddress) + except KeyError: + self.skipTest( + "Marvin configuration has no host credentials to\ + check router services") + details = {} + details['connect_ip'] = connect_ip + details['connect_user'] = connect_user + details['connect_passwd'] = connect_passwd + details['hypervisor'] = hypervisor + self.routerDetailsMap[router.id] = details + result = get_process_status( + self.routerDetailsMap[router.id]['connect_ip'], + 22, + self.routerDetailsMap[router.id]['connect_user'], + self.routerDetailsMap[router.id]['connect_passwd'], + router.linklocalip, + cmd, + hypervisor=self.routerDetailsMap[router.id]['hypervisor'] + ) + self.assertTrue(type(result) == list and len(result) > 0, + "%s on router %s returned invalid result" % (cmd, router.id)) + result = '\n'.join(result) + return result + + def getNetworkRouter(self, network, red_state="PRIMARY"): + routers = Router.list( + self.apiclient, + networkid=network.id, + listall=True + ) + self.assertTrue( + isinstance(routers, list) and len(routers) > 0, + "No routers found for network %s" % network.id + ) + if len(routers) == 1: + return routers[0] + for router in routers: + if router.redundantstate == red_state: + return router + + def getNetworkRoutes(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network.ip6routes, + None, + "IPv6 routes for network is empty") + return ipv6_network.ip6routes + + def isNetworkEgressDefaultPolicyAllow(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + if len(ipv6_network) == 1: + ipv6_network = ipv6_network[0] + return ipv6_network.egressdefaultpolicy + return False + + def checkRouterNicState(self, router, dev, state): + st = "state %s" % state + cmd = "ip link show %s | grep '%s'" % (dev, st) + res = self.getRouterProcessStatus(router, cmd) + self.assertTrue(type(res) == str and len(res) > 0 and st in res, + "%s failed on router %s" % (cmd, router.id)) + + def checkIpv6NetworkPrimaryRouter(self, router): + self.checkRouterNicState(router, "eth0", "UP") + guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth0", self.network.ip6gateway) + res = self.getRouterProcessStatus(router, guest_gateway_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and self.network.ip6gateway in res, + "%s failed on router %s" % (guest_gateway_check_cmd, router.id)) + self.assertFalse("dadfailed" in res, + "dadfailed for IPv6 guest gateway on router %s" % router.id) + self.checkRouterNicState(router, "eth2", "UP") + public_ipv6 = None + public_ipv6_gateway = None + nics = router.nic + for nic in nics: + if nic.traffictype == 'Public': + public_ipv6 = nic.ip6address + public_ipv6_gateway = nic.ip6gateway + break + self.assertNotEqual(public_ipv6, + None, + "IPv6 address for router Public NIC is empty") + public_ip_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth2", public_ipv6) + res = self.getRouterProcessStatus(router, public_ip_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and public_ipv6 in res, + "%s failed on router %s" % (public_ip_check_cmd, router.id)) + self.assertFalse("dadfailed" in res, + "dadfailed for public IPv6 on router %s" % router.id) + self.assertNotEqual(public_ipv6_gateway, + None, + "IPv6 gateway for router Public NIC is empty") + default_route_check_cmd = "ip -6 route | grep 'default via %s'" % (public_ipv6_gateway) + res = self.getRouterProcessStatus(router, default_route_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and public_ipv6_gateway in res, + "%s failed on router %s" % (default_route_check_cmd, router.id)) + + def checkIpv6NetworkBackupRouter(self, router): + self.checkRouterNicState(router, "eth0", "UP") + guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth0", self.network.ip6gateway) + res = self.getRouterProcessStatus(router, guest_gateway_check_cmd) + self.assertFalse(type(res) == str and len(res) > 0 and self.network.ip6gateway in res, + "%s failed on router %s" % (guest_gateway_check_cmd, router.id)) + self.checkRouterNicState(router, "eth2", "DOWN") + + def checkIpv6NetworkRoutersInternal(self): + for router in self.routers: + if router.state != "Running": + continue + if router.isredundantrouter == True and router.redundantstate == 'BACKUP': + self.checkIpv6NetworkBackupRouter(router) + continue + self.checkIpv6NetworkPrimaryRouter(router) + + + def checkIpv6NetworkVm(self): + self.debug("Listing NICS for VM %s in network: %s" % (self.virtual_machine.name, self.network.name)) + nics = NIC.list( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + networkid=self.network.id + ) + self.assertEqual( + len(nics), + 1, + "Router for the network isn't found" + ) + nic = nics[0] + self.assertNotEqual(nic.ip6address, + None, + "IPv6 address for VM %s NIC is empty" % nic.traffictype) + self.virtual_machine_ipv6_address = nic.ip6address + self.assertNotEqual(nic.ip6cidr, + None, + "IPv6 CIDR for VM %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6gateway, + None, + "IPv6 gateway for VM %s NIC is empty" % nic.traffictype) + + def restartNetworkWithCleanup(self): + self.network.restart(self.apiclient, cleanup=True) + + def updateNetworkWithOffering(self): + self.network.update(self.apiclient, networkofferingid=self.network_offering_update.id) + + def createIpv6FirewallRuleInNetwork(self, network_id, traffic_type, source_cidr, dest_cidr, protocol, + start_port, end_port, icmp_type, icmp_code): + cmd = createIpv6FirewallRule.createIpv6FirewallRuleCmd() + cmd.networkid = network_id + cmd.traffictype = traffic_type + if source_cidr: + cmd.cidrlist = source_cidr + if dest_cidr: + cmd.destcidrlist = dest_cidr + if protocol: + cmd.protocol = protocol + if start_port: + cmd.startport = start_port + if end_port: + cmd.endport = end_port + if icmp_type is not None: + cmd.icmptype = icmp_type + if icmp_code is not None: + cmd.icmpcode = icmp_code + fw_rule = self.apiclient.createIpv6FirewallRule(cmd) + return fw_rule + + def checkNetworkRouting(self): + self.routing_test_network_offering = self.createIpv6NetworkOfferingInternal(False, True) + self.cleanup.append(self.routing_test_network_offering) + self.services["network"]["networkoffering"] = self.routing_test_network_offering.id + self.routing_test_network = Network.create( + self.apiclient, + self.services["network"], + self.account.name, + self.account.domainid, + zoneid=self.zone.id + ) + self.cleanup.append(self.routing_test_network) + self.services["virtual_machine"]["zoneid"] = self.zone.id + self.routing_test_vm = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + networkids=[self.routing_test_network.id], + serviceofferingid=self.service_offering.id, + mode="advanced" + ) + self.cleanup.append(self.routing_test_vm) + + fw1 = self.createIpv6FirewallRuleInNetwork(self.routing_test_network.id, "Ingress", None, None, "icmp", + None, None, None, None) + fw2 = self.createIpv6FirewallRuleInNetwork(self.network.id, "Ingress", None, None, "icmp", + None, None, None, None) + + router = self.getNetworkRouter(self.routing_test_network) + self.logger.debug("Adding network routes in routing_test_network %s" % self.network_ipv6_routes) + for route in self.network_ipv6_routes: + add_route_cmd = "ip -6 route add %s via %s" % (route.subnet, route.gateway) + self.getRouterProcessStatus(router, add_route_cmd) + + router = self.getNetworkRouter(self.network) + routes = self.getNetworkRoutes(self.routing_test_network) + self.logger.debug("Adding routing_test_network routes in network %s" % routes) + for route in routes: + add_route_cmd = "ip -6 route add %s via %s" % (route.subnet, route.gateway) + self.getRouterProcessStatus(router, add_route_cmd) + + time.sleep(self.services["sleep"]) + + ping_cmd = "ping6 -c 4 %s" % self.virtual_machine_ipv6_address + res = self.getRouterProcessStatus(router, ping_cmd) + self.assertTrue(" 0% packet loss" in res, + "Ping from router %s of network %s to VM %s of network %s is unsuccessful" % (router.id, self.routing_test_network.id, self.virtual_machine.id, self.network.id)) + + ssh = self.routing_test_vm.get_ssh_client(retries=5) + res = ssh.execute(ping_cmd) + self.assertTrue(type(res) == list and len(res) > 0, + "%s on VM %s returned invalid result" % (ping_cmd, self.routing_test_vm.id)) + self.logger.debug(res) + res = '\n'.join(res) + self.assertTrue(" 0% packet loss" in res, + "Ping from VM %s of network %s to VM %s of network %s is unsuccessful" % (self.routing_test_vm.id, self.routing_test_network.id, self.virtual_machine.id, self.network.id)) + + cmd = deleteIpv6FirewallRule.deleteIpv6FirewallRuleCmd() + cmd.id = fw2.id + self.apiclient.deleteIpv6FirewallRule(cmd) + + def createAndVerifyIpv6FirewallRule(self, traffic_type, source_cidr, dest_cidr, protocol, + start_port, end_port, icmp_type, icmp_code, parsed_rule, delete=False): + self.logger.debug("createAndVerifyIpv6FirewallRule - %s" % parsed_rule) + fw_rule = self.createIpv6FirewallRuleInNetwork(self.network.id, traffic_type, source_cidr, dest_cidr, protocol, + start_port, end_port, icmp_type, icmp_code) + cmd = listIpv6FirewallRules.listIpv6FirewallRulesCmd() + cmd.id = fw_rule.id + rules = self.apiclient.listIpv6FirewallRules(cmd) + self.assertTrue( + isinstance(rules, list), + "Check listIpv6FirewallRules response returns a valid list" + ) + rule = rules[0] + self.assertEqual(rule.networkid, self.network.id, + "IPv6 firewall rule network ID mismatch %s, %s" % (rule.networkid, self.network.id)) + self.assertEqual(rule.traffictype, traffic_type, + "IPv6 firewall rule traffic type mismatch %s, %s" % (rule.traffictype, traffic_type)) + if source_cidr: + self.assertEqual(rule.cidrlist, source_cidr, + "IPv6 firewall rule source CIDR mismatch %s, %s" % (rule.cidrlist, source_cidr)) + if dest_cidr: + self.assertEqual(rule.destcidrlist, dest_cidr, + "IPv6 firewall rule destination CIDR mismatch %s, %s" % (rule.destcidrlist, dest_cidr)) + if protocol: + self.assertEqual(rule.protocol, protocol, + "IPv6 firewall rule protocol mismatch %s, %s" % (rule.protocol, protocol)) + if start_port: + self.assertEqual(rule.startport, start_port, + "IPv6 firewall rule start port mismatch %d, %d" % (rule.startport, start_port)) + if end_port: + self.assertEqual(rule.endport, end_port, + "IPv6 firewall rule end port mismatch %d, %d" % (rule.endport, end_port)) + if icmp_type is not None: + self.assertEqual(rule.icmptype, icmp_type, + "IPv6 firewall rule ICMP type mismatch %d, %d" % (rule.icmptype, icmp_type)) + if icmp_code is not None: + self.assertEqual(rule.icmpcode, icmp_code, + "IPv6 firewall rule ICMP code mismatch %d, %d" % (rule.icmpcode, icmp_code)) + routerCmd = "nft list chain ip6 %s %s" % (FIREWALL_TABLE, FIREWALL_CHAINS[traffic_type]) + res = self.getRouterProcessStatus(self.getNetworkRouter(self.network), routerCmd) + self.assertTrue(parsed_rule in res, + "Listing firewall rule with nft list chain failure for rule: %s" % parsed_rule) + if delete == True: + cmd = deleteIpv6FirewallRule.deleteIpv6FirewallRuleCmd() + cmd.id = fw_rule.id + self.apiclient.deleteIpv6FirewallRule(cmd) + res = self.getRouterProcessStatus(self.getNetworkRouter(self.network), routerCmd) + self.assertFalse(parsed_rule in res, + "Firewall rule present in nft list chain failure despite delete for rule: %s" % parsed_rule) + + def checkIpv6FirewallRule(self): + traffic_type = "Ingress" + + # Ingress - ip6 saddr SOURCE_CIDR ip6 daddr DEST_CIDR tcp dport { START_PORT-END_PORT } accept + source_cidr = self.getRandomIpv6Cidr() + dest_cidr = self.getRandomIpv6Cidr() + protocol = "tcp" + start_port = randint(3000, 5000) + end_port = start_port + randint(1, 8) + rule = "ip6 saddr %s ip6 daddr %s %s dport { %d-%d } accept" % (source_cidr, dest_cidr, protocol, start_port, end_port) + self.createAndVerifyIpv6FirewallRule(traffic_type, source_cidr, dest_cidr, protocol, + start_port, end_port, None, None, rule, True) + + # Ingress - ip6 daddr DEST_CIDR icmpv6 type TYPE code CODE accept + source_cidr = self.getRandomIpv6Cidr() + protocol = "icmp" + icmp_type = choice(list(ICMPV6_TYPE.keys())) + icmp_code = choice(list(ICMPV6_CODE_TYPE.keys())) + rule = "ip6 saddr %s ip6 daddr %s %sv6 type %s %sv6 code %s accept" % (source_cidr, CIDR_IPV6_ANY, protocol, ICMPV6_TYPE[icmp_type], protocol, ICMPV6_CODE_TYPE[icmp_code]) + self.createAndVerifyIpv6FirewallRule(traffic_type, source_cidr, None, protocol, + None, None, icmp_type, icmp_code, rule) + + action = "accept" + if self.isNetworkEgressDefaultPolicyAllow(self.network): + action = "drop" + traffic_type = "Egress" + + # Egress - ip6 saddr ::/0 ip6 daddr ::/0 udp dport { 0-65355 } ACTION + protocol = "udp" + rule = "ip6 saddr %s ip6 daddr %s %s dport %s %s" % (CIDR_IPV6_ANY, CIDR_IPV6_ANY, protocol, TCP_UDP_PORT_ANY, action) + self.createAndVerifyIpv6FirewallRule(traffic_type, None, None, protocol, + None, None, None, None, rule) + + # Egress - ip6 saddr ::/0 ip6 daddr ::/0 icmpv6 type ANY_TYPE ACTION + protocol = "icmp" + rule = "ip6 saddr %s ip6 daddr %s %sv6 type %s %s" % (CIDR_IPV6_ANY, CIDR_IPV6_ANY, protocol, ICMPV6_TYPE_ANY, action) + self.createAndVerifyIpv6FirewallRule(traffic_type, None, None, protocol, + None, None, None, None, rule) + + # Egress - ip6 saddr ::/0 ip6 daddr DEST_CIDR ACTION + protocol = "all" + dest_cidr = self.getRandomIpv6Cidr() + rule = "ip6 saddr %s ip6 daddr %s %s" % (CIDR_IPV6_ANY, CIDR_IPV6_ANY, action) + self.createAndVerifyIpv6FirewallRule(traffic_type, None, None, protocol, + None, None, None, None, rule) + + def checkNetworkVRRedundancy(self): + primary_router = self.getNetworkRouter(self.network) + Router.stop( + self.apiclient, + id=primary_router.id + ) + time.sleep(self.services["sleep"]/2) + new_primary_router = self.getNetworkRouter(self.network) + self.assertNotEqual(new_primary_router.id, primary_router.id, + "Original primary router ID: %s of network is still the primary router after stopping" % (primary_router.id)) + print(new_primary_router) + self.checkIpv6NetworkPrimaryRouter(new_primary_router) + + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + @skipTestIf("ipv6NotSupported") + def test_01_verify_ipv6_network(self): + """Test to verify IPv6 network + + # Validate the following: + # 1. Create IPv6 network, deploy VM + # 2. Verify network has required IPv6 details + # 3. List router for the network and verify it has required IPv6 details for Guest and Public NIC of the VR + # 4. SSH into VR(s) and verify correct details are present for its NICs + # 5. Verify VM in network has required IPv6 details + # 6. Restart network with cleanup + # 7. Update network with a new offering + # 8. Again verify network and VR details + # 9. Deploy another IPv6 network and check routing between two networks and their VM + # 10. Create IPv6 firewall rules and verify in VR if they get implemented + """ + + self.createIpv6NetworkOffering(False) + self.createIpv6NetworkOfferingForUpdate(False) + self.createTinyServiceOffering() + self.deployIpv6Network() + self.deployIpv6NetworkVm() + self.checkIpv6NetworkBasic() + self.checkIpv6NetworkRoutersBasic() + self.checkIpv6NetworkRoutersInternal() + self.checkIpv6NetworkVm() + self.restartNetworkWithCleanup() + self.updateNetworkWithOffering() + self.checkIpv6NetworkBasic() + self.checkIpv6NetworkRoutersBasic() + self.checkNetworkRouting() + self.checkIpv6FirewallRule() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + @skipTestIf("ipv6NotSupported") + def test_02_verify_ipv6_network_redundant(self): + """Test to verify redundant IPv6 network + + # Validate the following: + # 1. Create IPv6 network, deploy VM + # 2. Verify network has required IPv6 details + # 3. List VRs for the network and verify it has required IPv6 details for Guest and Public NIC of the VR + # 4. SSH into VR(s) and verify correct details are present for its NICs + # 5. Verify VM in network has required IPv6 details + # 6. Restart network with cleanup + # 7. Update network with a new offering + # 8. Again verify network and VR details + # 9. Deploy another IPv6 network and check routing between two networks and their VM + # 10. Create IPv6 firewall rules and verify in VR if they get implemented + # 11. Stop primary router and verify internals in backup VR + """ + + self.createIpv6NetworkOffering(True) + self.createIpv6NetworkOfferingForUpdate(True) + self.createTinyServiceOffering() + self.deployIpv6Network() + self.deployIpv6NetworkVm() + self.checkIpv6NetworkBasic() + self.checkIpv6NetworkRoutersBasic() + self.checkIpv6NetworkRoutersInternal() + self.checkIpv6NetworkVm() + self.restartNetworkWithCleanup() + self.updateNetworkWithOffering() + self.checkIpv6NetworkBasic() + self.checkIpv6NetworkRoutersBasic() + self.checkNetworkRouting() + self.checkIpv6FirewallRule() + self.checkNetworkVRRedundancy() diff --git a/tools/appliance/systemvmtemplate/template.json b/tools/appliance/systemvmtemplate/template.json index 31eb2ea5abf..960f24a75ff 100644 --- a/tools/appliance/systemvmtemplate/template.json +++ b/tools/appliance/systemvmtemplate/template.json @@ -27,8 +27,8 @@ "format": "qcow2", "headless": true, "http_directory": "http", - "iso_checksum": "sha512:c685b85cf9f248633ba3cd2b9f9e781fa03225587e0c332aef2063f6877a1f0622f56d44cf0690087b0ca36883147ecb5593e3da6f965968402cdbdf12f6dd74", - "iso_url": "https://cdimage.debian.org/debian-cd/11.2.0/amd64/iso-cd/debian-11.2.0-amd64-netinst.iso", + "iso_checksum": "sha512:2810f894afab9ac2631ddd097599761c1481b85e629d6a3197fe1488713af048d37241eb85def681ba86e62b406dd9b891ee1ae7915416335b6bb000d57c1e53", + "iso_url": "https://cdimage.debian.org/debian-cd/11.3.0/amd64/iso-cd/debian-11.3.0-amd64-netinst.iso", "net_device": "virtio-net", "output_directory": "../dist", "qemuargs": [ diff --git a/tools/marvin/marvin/config/test_data.py b/tools/marvin/marvin/config/test_data.py index 5ddf07eea49..74303083ac9 100644 --- a/tools/marvin/marvin/config/test_data.py +++ b/tools/marvin/marvin/config/test_data.py @@ -49,6 +49,15 @@ test_data = { "forvirtualnetwork": "true", "vlan": "300" }, + "publicip6range": { + "ip6gateway": "fd17:ac56:1234:2000::1", + "ip6cidr": "fd17:ac56:1234:2000::/64", + "vlan": 301, + "forvirtualnetwork": "true" + }, + "guestip6prefix": { + "prefix": "fd17:ac56:1234:1000::/52" + }, "private_gateway": { "ipaddress": "172.16.1.2", "gateway": "172.16.1.1", diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 4d18760ae94..274b8b92528 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -2423,7 +2423,6 @@ class NetworkOffering: @classmethod def create(cls, apiclient, services, **kwargs): """Create network offering""" - cmd = createNetworkOffering.createNetworkOfferingCmd() cmd.displaytext = "-".join([services["displaytext"], random_gen()]) cmd.name = "-".join([services["name"], random_gen()]) @@ -2462,6 +2461,8 @@ class NetworkOffering: cmd.egressdefaultpolicy = services["egress_policy"] if "tags" in services: cmd.tags = services["tags"] + if "internetprotocol" in services: + cmd.internetprotocol = services["internetprotocol"] cmd.details = [{}] if "servicepackageuuid" in services: cmd.details[0]["servicepackageuuid"] = services["servicepackageuuid"] @@ -3630,15 +3631,23 @@ class PublicIpRange: """Create VlanIpRange""" cmd = createVlanIpRange.createVlanIpRangeCmd() - cmd.gateway = services["gateway"] - cmd.netmask = services["netmask"] + if "gateway" in services: + cmd.gateway = services["gateway"] + if "netmask" in services: + cmd.netmask = services["netmask"] cmd.forvirtualnetwork = services["forvirtualnetwork"] - cmd.startip = services["startip"] - cmd.endip = services["endip"] + if "startip" in services: + cmd.startip = services["startip"] + if "endip" in services: + cmd.endip = services["endip"] cmd.zoneid = services["zoneid"] if "podid" in services: cmd.podid = services["podid"] cmd.vlan = services["vlan"] + if "ip6gateway" in services: + cmd.ip6gateway = services["ip6gateway"] + if "ip6cidr" in services: + cmd.ip6cidr = services["ip6cidr"] if account: cmd.account = account @@ -4616,6 +4625,8 @@ class VpcOffering: 'capabilitytype': ctype, 'capabilityvalue': value }) + if "internetprotocol" in services: + cmd.internetprotocol = services["internetprotocol"] return VpcOffering(apiclient.createVPCOffering(cmd).__dict__) def update(self, apiclient, name=None, displaytext=None, state=None): diff --git a/tools/marvin/marvin/sandbox/demo/simulator/testcase/libs/base.py b/tools/marvin/marvin/sandbox/demo/simulator/testcase/libs/base.py index 17c843e743d..b28fc1c1e0f 100644 --- a/tools/marvin/marvin/sandbox/demo/simulator/testcase/libs/base.py +++ b/tools/marvin/marvin/sandbox/demo/simulator/testcase/libs/base.py @@ -1316,14 +1316,23 @@ class PublicIpRange: """Create VlanIpRange""" cmd = createVlanIpRange.createVlanIpRangeCmd() - cmd.gateway = services["gateway"] - cmd.netmask = services["netmask"] + if "gateway" in services: + cmd.gateway = services["gateway"] + if "netmask" in services: + cmd.netmask = services["netmask"] cmd.forvirtualnetwork = services["forvirtualnetwork"] - cmd.startip = services["startip"] - cmd.endip = services["endip"] + if "startip" in services: + cmd.startip = services["startip"] + if "endip" in services: + cmd.endip = services["endip"] cmd.zoneid = services["zoneid"] - cmd.podid = services["podid"] + if "podid" in services: + cmd.podid = services["podid"] cmd.vlan = services["vlan"] + if "ip6gateway" in services: + cmd.ip6gateway = services["ip6gateway"] + if "ip6cidr" in services: + cmd.ip6cidr = services["ip6cidr"] return PublicIpRange(apiclient.createVlanIpRange(cmd).__dict__) diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 2ec4c84a562..acd3921a846 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -75,6 +75,7 @@ "label.action.attach.iso.processing": "Attaching ISO....", "label.action.bulk.delete.egress.firewall.rules": "Bulk delete egress firewall rules", "label.action.bulk.delete.firewall.rules": "Bulk delete firewall rules", +"label.action.bulk.delete.ip.v6.firewall.rules": "Bulk remove IPv6 firewall rules", "label.action.bulk.delete.isos": "Bulk delete ISOs", "label.action.bulk.delete.load.balancer.rules": "Bulk delete load balancer rules", "label.action.bulk.delete.portforward.rules": "Bulk delete Port Forward rules", @@ -332,6 +333,7 @@ "label.add.intermediate.certificate": "Add intermediate certificate", "label.add.internal.lb": "Add Internal LB", "label.add.ip.range": "Add IP Range", +"label.add.ip.v6.prefix": "Add IPv6 Prefix", "label.add.isolated.guest.network": "Add Isolated Guest Network", "label.add.isolated.guest.network.with.sourcenat": "Add Isolated Guest Network with SourceNat", "label.add.isolated.network": "Add Isolated Network", @@ -390,6 +392,7 @@ "label.add.traffic.type": "Add Traffic Type", "label.add.ucs.manager": "Add UCS Manager", "label.add.user": "Add User", +"label.add.upstream.ipv6.routes": "Add upstream IPv6 routes", "label.add.vlan": "Add VLAN", "label.add.vm": "Add VM", "label.add.vm.to.tier": "Add VM to tier", @@ -444,6 +447,7 @@ "label.alerts": "Alerts", "label.algorithm": "Algorithm", "label.all": "All", +"label.all.ipv6": "All IPv6", "label.all.zone": "All Zones", "label.allocated": "Allocated", "label.allocatedonly": "Allocated", @@ -628,6 +632,7 @@ "label.configure.vpc": "Configure VPC", "label.confirm.delete.egress.firewall.rules": "Please confirm you wish to delete the selected egress firewall rules", "label.confirm.delete.firewall.rules": "Please confirm you wish to delete the selected firewall rules", +"label.confirm.delete.ip.v6.firewall.rules": "Please confirm you wish to delete the selected IPv6 firewall rules", "label.confirm.delete.isos": "Please confirm you wish to delete the selected isos", "label.confirm.delete.loadbalancer.rules": "Please confirm you wish to delete the selected load balancing rules", "label.confirm.delete.portforward.rules": "Please confirm you wish to delete the selected port-forward rules", @@ -752,6 +757,7 @@ "label.delete.icon": "Delete icon", "label.delete.instance.group": "Delete Instance Group", "label.delete.internal.lb": "Delete Internal LB", +"label.delete.ip.v6.prefix": "Delete IPv6 prefix", "label.delete.netscaler": "Delete NetScaler", "label.delete.niciranvp": "Remove Nvp Controller", "label.delete.opendaylight.device": "Delete OpenDaylight Controller", @@ -1114,6 +1120,7 @@ "label.hypervnetworklabel": "HyperV Traffic Label", "label.i.accept.all.license.agreements": "I accept all license agreement", "label.icmp": "ICMP", +"label.icmp6": "ICMPv6", "label.icmpcode": "ICMP Code", "label.icmpcode.end.port": "ICMP Code / End Port", "label.icmptype": "ICMP Type", @@ -1175,6 +1182,7 @@ "label.internallb.name.description": "Unique name for Internal LB", "label.internallb.sourceip.description": "Brief description of the Internal LB", "label.internallbvm": "InternalLbVm", +"label.internetprotocol": "Internet Protocol", "label.interval": "Polling Interval (in sec)", "label.intervaltype": "Interval Type", "label.introduction.to.cloudstack": "Introduction to CloudStack™", @@ -1185,6 +1193,14 @@ "label.invite.to": "Invite to", "label.invited.accounts": "Invited accounts", "label.ip": "IP Address", +"label.ip6firewall": "IPv6 Firewall", +"label.ip6routes": "IPv6 Routes", +"label.ip6routing": "IPv6 Routing", +"label.ip.range.type": "IP Range Type", +"label.ip.v4": "IPv4", +"label.ip.v6": "IPv6", +"label.ip.v4.v6": "IPv4 + IPv6 (Dual Stack)", +"label.ip.v6.firewall": "IPv6 Firewall", "label.ip.allocations": "IP Allocations", "label.ip.or.fqdn": "IP or FQDN", "label.ip.range": "IP Range", @@ -1211,6 +1227,7 @@ "label.ipv4.dns2": "IPv4 DNS2", "label.ipv6.dns1": "IPv6 DNS1", "label.ipv6.dns2": "IPv6 DNS2", +"label.ipv6.subnets": "IPv6 Subnets", "label.iqn": "Target IQN", "label.is.in.progress": "is in progress", "label.is.redundant.router": "Redundant", @@ -1732,6 +1749,7 @@ "label.powerflex.storage.pool": "Storage Pool", "label.powerstate": "Power State", "label.preferred": "Preferred", +"label.prefix": "Prefix", "label.presetup": "PreSetup", "label.prev": "Prev", "label.previous": "Previous", @@ -2242,6 +2260,7 @@ "label.tariffvalue": "Tariff Value", "label.task.completed": "Task completed", "label.tcp": "TCP", +"label.tcp6": "TCPv6", "label.tcp.proxy": "TCP Proxy", "label.template": "Select a template", "label.template.select.existing": "Select an existing template", @@ -2322,6 +2341,7 @@ "label.type.id": "Type ID", "label.ucs": "UCS", "label.udp": "UDP", +"label.udp6": "UDPv6", "label.uk.keyboard": "UK keyboard", "label.unauthorized": "Unauthorized", "label.unavailable": "Unavailable", @@ -2684,6 +2704,13 @@ "message.add.ip.range.direct.network": "Add an IP range to direct network in zone ", "message.add.ip.range.to.pod": "

Add an IP range to pod:

", "message.add.iprange.processing": "Adding IP Range...", +"message.add.ip.v6.prefix.processing": "Adding IPv6 Prefix...", +"message.add.ip.v6.firewall.rule.failed": "Failed to add IPv6 firewall rule", +"message.add.ip.v6.firewall.rule.processing": "Adding IPv6 firewall rule...", +"message.add.ip.v6.firewall.rule.success": "Added IPv6 firewall rule", +"message.remove.ip.v6.firewall.rule.failed": "Failed to remove IPv6 firewall rule", +"message.remove.ip.v6.firewall.rule.processing": "Removing IPv6 firewall rule...", +"message.remove.ip.v6.firewall.rule.success": "Removed IPv6 firewall rule", "message.add.load.balancer": "Add a load balancer to zone", "message.add.load.balancer.under.ip": "The load balancer rule has been added under IP:", "message.add.network": "Add a new network for zone: ", @@ -2894,6 +2921,7 @@ "message.delete.backup": "Are you sure you want to delete the backup?", "message.delete.failed": "Delete fail", "message.delete.gateway": "Please confirm you want to delete the gateway", +"message.delete.ip.v6.prefix.processing": "Deleting IPv6 prefix...", "message.delete.port.forward.processing": "Deleting Port Forwarding rule...", "message.delete.project": "Are you sure you want to delete this project?", "message.delete.rule.processing": "Deleting rule...", @@ -2955,7 +2983,7 @@ "message.edit.traffic.type": "Please specify the traffic label you want associated with this traffic type.", "message.egress.rules.allow": "Allow (traffic matching the egress rules added will be denied)", "message.egress.rules.deny": "Deny (traffic matching the egress rules added will be allowed)", -"message.egress.rules.info.for.network": "The default egress policy of this network is %x.
Outgoing traffic matching the following rules will be %y", +"message.egress.rules.info.for.network": "The default egress policy of this network is %x.
Outgoing traffic matching the following egress rules will be %y", "message.enable.account": "Please confirm that you want to enable this account.", "message.enable.netsacler.provider.failed": "failed to enable Netscaler provider", "message.enable.securitygroup.provider.failed": "failed to enable security group provider", @@ -3161,6 +3189,7 @@ "message.interloadbalance.not.return.elementid": "error: listInternalLoadBalancerElements API doesn't return Internal LB Element Id", "message.ip.address.changed": "Your IP addresses may have changed; would you like to refresh the listing? Note that in this case the details pane will close.", "message.ip.address.changes.effect.after.vm.restart": "IP address changes takes effect only after VM restart.", +"message.ip.v6.prefix.delete": "IPv6 prefix deleted", "message.iso.desc": "Disc image containing data or bootable media for OS", "message.join.project": "You have now joined a project. Please switch to Project view to see the project.", "message.kubeconfig.cluster.not.available": "Kubernetes cluster kubeconfig not available currently", @@ -3241,6 +3270,8 @@ "message.number.pods": "

# of Pods

", "message.number.storage": "

# of Primary Storage Volumes

", "message.number.zones": "

# of Zones

", +"message.offering.internet.protocol.warning": "WARNING: IPv6 supported networks use static routing and will require upstream routes to be configured manually.", +"message.offering.ipv6.warning": "Please refer documentation for creating IPv6 enabled network/VPC offering IPv6 support in CloudStack - Isolated networks and VPC tiers", "message.outofbandmanagement.action.maintenance": "Warning host is in maintenance mode", "message.ovf.configurations": "OVF configurations available for the selected appliance. Please select the desired value. Incompatible compute offerings will get disbaled.", "message.ovf.properties.available": "There are OVF properties available for customizing the selected appliance. Please edit the values accordingly.", @@ -3365,6 +3396,7 @@ "message.success.add.firewall.rule": "Successfully added new Firewall rule", "message.success.add.guest.network": "Successfully created guest network", "message.success.add.iprange": "Successfully added IP Range", +"message.success.add.ip.v6.prefix": "Successfully added IPv6 Prefix", "message.success.add.kuberversion": "Successfully added Kubernetes version", "message.success.add.network": "Successfully added network", "message.success.add.network.acl": "Successfully added Network ACL List", diff --git a/ui/src/components/view/DetailsTab.vue b/ui/src/components/view/DetailsTab.vue index 316a951c2b1..3c260c05470 100644 --- a/ui/src/components/view/DetailsTab.vue +++ b/ui/src/components/view/DetailsTab.vue @@ -16,11 +16,16 @@ // under the License.