diff --git a/api/src/main/java/com/cloud/network/Network.java b/api/src/main/java/com/cloud/network/Network.java index 2f0bcdd5ef9a..da3eda036e0a 100644 --- a/api/src/main/java/com/cloud/network/Network.java +++ b/api/src/main/java/com/cloud/network/Network.java @@ -461,6 +461,16 @@ public void setIp6Address(String ip6Address) { void setCidr(String cidr); + // For an isolated guest network, an optional custom DHCP range within the CIDR that guest IPs + // are allocated from; null means the whole CIDR is used, which is the default. + String getDhcpStartIp(); + + void setDhcpStartIp(String dhcpStartIp); + + String getDhcpEndIp(); + + void setDhcpEndIp(String dhcpEndIp); + // "networkcidr" is the network CIDR of the guest network which uses IP reservation. // It is the summation of "cidr" and the reservedIPrange(the address space used for non CloudStack purposes). // For networks not configured with IP reservation, "networkcidr" is always null diff --git a/api/src/main/java/com/cloud/network/NetworkProfile.java b/api/src/main/java/com/cloud/network/NetworkProfile.java index d690344a0e38..689bbc47bd90 100644 --- a/api/src/main/java/com/cloud/network/NetworkProfile.java +++ b/api/src/main/java/com/cloud/network/NetworkProfile.java @@ -44,6 +44,8 @@ public class NetworkProfile implements Network { private TrafficType trafficType; private String gateway; private String cidr; + private String dhcpStartIp; + private String dhcpEndIp; private final String networkCidr; private final String ip6Gateway; private final String ip6Cidr; @@ -79,6 +81,8 @@ public NetworkProfile(Network network) { gateway = network.getGateway(); cidr = network.getCidr(); networkCidr = network.getNetworkCidr(); + dhcpStartIp = network.getDhcpStartIp(); + dhcpEndIp = network.getDhcpEndIp(); ip6Gateway = network.getIp6Gateway(); ip6Cidr = network.getIp6Cidr(); networkOfferingId = network.getNetworkOfferingId(); @@ -228,6 +232,26 @@ public void setCidr(String cidr) { this.cidr = cidr; } + @Override + public String getDhcpStartIp() { + return dhcpStartIp; + } + + @Override + public void setDhcpStartIp(String dhcpStartIp) { + this.dhcpStartIp = dhcpStartIp; + } + + @Override + public String getDhcpEndIp() { + return dhcpEndIp; + } + + @Override + public void setDhcpEndIp(String dhcpEndIp) { + this.dhcpEndIp = dhcpEndIp; + } + @Override public String getNetworkCidr() { return networkCidr; 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 aeaf333540c7..13651cb303dc 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 @@ -67,6 +67,14 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement @Param(description = "CloudStack managed address space, all CloudStack managed Instances get IP address from CIDR") private String cidr; + @SerializedName(ApiConstants.START_IP) + @Param(description = "the start of the custom DHCP range for an isolated network, within its CIDR", since = "4.24.0") + private String dhcpStartIp; + + @SerializedName(ApiConstants.END_IP) + @Param(description = "the end of the custom DHCP range for an isolated network, within its CIDR", since = "4.24.0") + private String dhcpEndIp; + @SerializedName(ApiConstants.NETWORK_CIDR) @Param(description = "The Network CIDR of the guest Network configured with IP reservation. It is the summation of CIDR and RESERVED_IP_RANGE") private String networkCidr; @@ -511,6 +519,14 @@ public void setCidr(String cidr) { this.cidr = cidr; } + public void setDhcpStartIp(String dhcpStartIp) { + this.dhcpStartIp = dhcpStartIp; + } + + public void setDhcpEndIp(String dhcpEndIp) { + this.dhcpEndIp = dhcpEndIp; + } + public void setNetworkCidr(String networkCidr) { this.networkCidr = networkCidr; } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java index f2572ba91c21..c17b5c407c29 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java @@ -82,6 +82,12 @@ public class NetworkVO implements Network { @Column(name = "network_cidr") String networkCidr; + @Column(name = "dhcp_start_ip") + String dhcpStartIp; + + @Column(name = "dhcp_end_ip") + String dhcpEndIp; + @Column(name = "network_offering_id") long networkOfferingId; @@ -470,6 +476,26 @@ public void setCidr(String cidr) { this.cidr = cidr; } + @Override + public String getDhcpStartIp() { + return dhcpStartIp; + } + + @Override + public void setDhcpStartIp(String dhcpStartIp) { + this.dhcpStartIp = dhcpStartIp; + } + + @Override + public String getDhcpEndIp() { + return dhcpEndIp; + } + + @Override + public void setDhcpEndIp(String dhcpEndIp) { + this.dhcpEndIp = dhcpEndIp; + } + // "networkcidr" is the network CIDR of the guest network which is configured with IP reservation feature // It is the summation of "cidr" and the reservedIPrange(the address space used for non cloudstack purposes.) // For networks not using IP reservation "networkcidr" is always null diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql index 863ee93bc01a..42aed98b903a 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql @@ -100,3 +100,6 @@ CREATE TABLE IF NOT EXISTS `cloud`.`instance_boot_group_details` ( PRIMARY KEY (`id`), CONSTRAINT `fk_instance_boot_group_details__group_id` FOREIGN KEY (`boot_group_id`) REFERENCES `instance_boot_group` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +-- Custom DHCP (guest IP allocation) range for isolated guest networks +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'dhcp_start_ip', 'VARCHAR(15) DEFAULT NULL COMMENT ''start of the custom DHCP range for an isolated network, within its CIDR'' '); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'dhcp_end_ip', 'VARCHAR(15) DEFAULT NULL COMMENT ''end of the custom DHCP range for an isolated network, within its CIDR'' '); diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index deba6d43f03d..376403860cae 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -2603,6 +2603,8 @@ public NetworkResponse createNetworkResponse(ResponseView view, Network network) // FIXME - either set netmask or cidr response.setCidr(cidr); + response.setDhcpStartIp(network.getDhcpStartIp()); + response.setDhcpEndIp(network.getDhcpEndIp()); if (network.getNetworkCidr() != null) { response.setNetworkCidr((network.getNetworkCidr())); } diff --git a/server/src/main/java/com/cloud/network/NetworkModelImpl.java b/server/src/main/java/com/cloud/network/NetworkModelImpl.java index f47046cdc434..cd9273cff6db 100644 --- a/server/src/main/java/com/cloud/network/NetworkModelImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkModelImpl.java @@ -2333,6 +2333,12 @@ public Set getAvailableIps(Network network, String requestedIp) { if ((gateway != null) && (allPossibleIps.contains(NetUtils.ip2Long(gateway)))) allPossibleIps.remove(NetUtils.ip2Long(gateway)); + if (StringUtils.isNoneBlank(network.getDhcpStartIp(), network.getDhcpEndIp())) { + long start = NetUtils.ip2Long(network.getDhcpStartIp()); + long end = NetUtils.ip2Long(network.getDhcpEndIp()); + allPossibleIps.removeIf(ip -> ip < start || ip > end); + } + return allPossibleIps; } diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 2853fa96330d..9a3f49ee466a 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -1834,10 +1834,23 @@ public Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapac } } + // For an isolated network a start/end IP defines a custom DHCP range within the CIDR that guest IPs are + // allocated from; without it the whole CIDR is used, which is the default. Validate it up front so a bad + // range is rejected before the network is persisted, rather than throwing after commit and leaving an + // orphaned network. + boolean hasIsolatedDhcpRange = ntwkOff.getGuestType() == GuestType.Isolated && StringUtils.isNotBlank(startIP); + if (hasIsolatedDhcpRange) { + validateIsolatedNetworkDhcpRange(startIP, endIP, cidr, gateway); + } + Network network = commitNetwork(networkOfferingId, gateway, startIP, endIP, netmask, networkDomain, vlanId, bypassVlanOverlapCheck, name, displayText, caller, physicalNetworkId, zone.getId(), domainId, isDomainSpecific, subdomainAccess, vpcId, startIPv6, endIPv6, ip6Gateway, ip6Cidr, displayNetwork, aclId, secondaryVlanId, privateVlanType, ntwkOff, pNtwk, aclType, owner, cidr, createVlan, externalId, routerIPv4, routerIPv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic); + if (hasIsolatedDhcpRange) { + storeIsolatedNetworkDhcpRange(network.getId(), startIP, endIP); + } + // retrieve, acquire and associate the correct IP addresses checkAndSetRouterSourceNatIp(owner, cmd, network); @@ -1872,6 +1885,32 @@ private boolean isNonVpcNetworkSupportingDynamicRouting(NetworkOffering networkO return !networkOffering.isForVpc() && NetworkOffering.RoutingMode.Dynamic == networkOffering.getRoutingMode(); } + protected void validateIsolatedNetworkDhcpRange(String startIP, String endIP, String cidr, String gateway) { + if (endIP == null) { + endIP = startIP; + } + if (!NetUtils.isIpWithInCidrRange(startIP, cidr) || !NetUtils.isIpWithInCidrRange(endIP, cidr)) { + throw new InvalidParameterValueException(String.format("The DHCP range %s-%s is not within the network CIDR %s", startIP, endIP, cidr)); + } + if (NetUtils.ip2Long(startIP) > NetUtils.ip2Long(endIP)) { + throw new InvalidParameterValueException(String.format("The DHCP start IP %s is greater than the end IP %s", startIP, endIP)); + } + if (gateway != null && NetUtils.ip2Long(gateway) >= NetUtils.ip2Long(startIP) && NetUtils.ip2Long(gateway) <= NetUtils.ip2Long(endIP)) { + throw new InvalidParameterValueException(String.format("The DHCP range %s-%s must not include the gateway %s", startIP, endIP, gateway)); + } + } + + protected void storeIsolatedNetworkDhcpRange(long networkId, String startIP, String endIP) { + NetworkVO network = _networksDao.findById(networkId); + if (endIP == null) { + endIP = startIP; + } + validateIsolatedNetworkDhcpRange(startIP, endIP, network.getCidr(), network.getGateway()); + network.setDhcpStartIp(startIP); + network.setDhcpEndIp(endIP); + _networksDao.update(networkId, network); + } + private void validateNetworkCreationSupported(long zoneId, String zoneName, GuestType guestType) { NsxProviderVO nsxProviderVO = nsxProviderDao.findByZoneId(zoneId); if (Objects.nonNull(nsxProviderVO) && GuestType.L2.equals(guestType)) { @@ -3409,6 +3448,17 @@ public Network updateGuestNetwork(final UpdateNetworkCmd cmd) { } } + // A custom DHCP range must stay within the reserved Guest VM CIDR; reject a reservation that would + // leave the stored range partly or wholly outside it, otherwise guest IP allocation could be starved. + if (StringUtils.isNoneBlank(network.getDhcpStartIp(), network.getDhcpEndIp())) { + long dhcpStart = NetUtils.ip2Long(network.getDhcpStartIp()); + long dhcpEnd = NetUtils.ip2Long(network.getDhcpEndIp()); + if (dhcpStart < startIp || dhcpEnd > endIp) { + throw new InvalidParameterValueException(String.format("The network has a custom DHCP range %s-%s that falls outside the requested Guest VM CIDR %s. Remove or adjust the DHCP range before applying the reservation.", + network.getDhcpStartIp(), network.getDhcpEndIp(), guestVmCidr)); + } + } + // 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) { diff --git a/server/src/test/java/com/cloud/network/NetworkModelImplTest.java b/server/src/test/java/com/cloud/network/NetworkModelImplTest.java index 7810662c617b..9f4ffe200d3f 100644 --- a/server/src/test/java/com/cloud/network/NetworkModelImplTest.java +++ b/server/src/test/java/com/cloud/network/NetworkModelImplTest.java @@ -69,6 +69,7 @@ import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; import com.cloud.utils.Pair; import com.cloud.utils.net.Ip; +import com.cloud.utils.net.NetUtils; import com.cloud.vm.Nic; import com.cloud.vm.NicProfile; import com.cloud.vm.VirtualMachine; @@ -452,4 +453,43 @@ public void listSupportedNetworkServiceProvidersExcludesExtensionBackedProviders Mockito.verify(physicalNetworkServiceProviderDao, Mockito.times(1)).listAll(); Mockito.verify(physicalNetworkServiceProviderDao, Mockito.never()).listBy(Mockito.anyLong()); } + + private NetworkVO networkWithDhcpRange(String cidr, String gateway, String dhcpStart, String dhcpEnd) { + NetworkVO network = mock(NetworkVO.class); + when(network.getCidr()).thenReturn(cidr); + when(network.getGateway()).thenReturn(gateway); + when(network.getDhcpStartIp()).thenReturn(dhcpStart); + when(network.getDhcpEndIp()).thenReturn(dhcpEnd); + Mockito.doReturn(new ArrayList()).when(networkModel).getUsedIpsInNetwork(network); + return network; + } + + @Test + public void getAvailableIpsRestrictsToTheDhcpRangeWhenSet() { + NetworkVO network = networkWithDhcpRange("10.1.1.0/24", "10.1.1.1", "10.1.1.10", "10.1.1.20"); + + Set ips = networkModel.getAvailableIps(network, null); + + long start = NetUtils.ip2Long("10.1.1.10"); + long end = NetUtils.ip2Long("10.1.1.20"); + for (Long ip : ips) { + assertTrue(ip >= start && ip <= end); + } + assertEquals(11, ips.size()); + assertTrue(ips.contains(NetUtils.ip2Long("10.1.1.10"))); + assertTrue(ips.contains(NetUtils.ip2Long("10.1.1.20"))); + assertFalse(ips.contains(NetUtils.ip2Long("10.1.1.9"))); + assertFalse(ips.contains(NetUtils.ip2Long("10.1.1.21"))); + } + + @Test + public void getAvailableIpsUsesTheWholeCidrWhenNoDhcpRangeIsSet() { + NetworkVO network = networkWithDhcpRange("10.1.1.0/24", "10.1.1.1", null, null); + + Set ips = networkModel.getAvailableIps(network, null); + + assertTrue(ips.contains(NetUtils.ip2Long("10.1.1.10"))); + assertTrue(ips.contains(NetUtils.ip2Long("10.1.1.250"))); + assertFalse(ips.contains(NetUtils.ip2Long("10.1.1.1"))); + } } diff --git a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java index cd7d40d68951..1e966cb698e3 100644 --- a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java +++ b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java @@ -1378,4 +1378,62 @@ public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturn Assert.assertFalse(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(false, networkOfferingVO)); } + + private NetworkVO isolatedNetworkForDhcpRange() { + NetworkVO network = Mockito.mock(NetworkVO.class); + Mockito.when(network.getCidr()).thenReturn("10.1.1.0/24"); + Mockito.when(network.getGateway()).thenReturn("10.1.1.1"); + Mockito.when(networkDao.findById(5L)).thenReturn(network); + return network; + } + + @Test + public void storeIsolatedNetworkDhcpRangeStoresAValidRange() { + NetworkVO network = isolatedNetworkForDhcpRange(); + + service.storeIsolatedNetworkDhcpRange(5L, "10.1.1.10", "10.1.1.20"); + + Mockito.verify(network).setDhcpStartIp("10.1.1.10"); + Mockito.verify(network).setDhcpEndIp("10.1.1.20"); + Mockito.verify(networkDao).update(5L, network); + } + + @Test + public void storeIsolatedNetworkDhcpRangeDefaultsEndToStart() { + NetworkVO network = isolatedNetworkForDhcpRange(); + + service.storeIsolatedNetworkDhcpRange(5L, "10.1.1.10", null); + + Mockito.verify(network).setDhcpStartIp("10.1.1.10"); + Mockito.verify(network).setDhcpEndIp("10.1.1.10"); + } + + @Test(expected = InvalidParameterValueException.class) + public void storeIsolatedNetworkDhcpRangeRejectsRangeOutsideCidr() { + isolatedNetworkForDhcpRange(); + service.storeIsolatedNetworkDhcpRange(5L, "10.2.2.10", "10.2.2.20"); + } + + @Test(expected = InvalidParameterValueException.class) + public void storeIsolatedNetworkDhcpRangeRejectsStartGreaterThanEnd() { + isolatedNetworkForDhcpRange(); + service.storeIsolatedNetworkDhcpRange(5L, "10.1.1.20", "10.1.1.10"); + } + + @Test(expected = InvalidParameterValueException.class) + public void storeIsolatedNetworkDhcpRangeRejectsRangeIncludingGateway() { + isolatedNetworkForDhcpRange(); + service.storeIsolatedNetworkDhcpRange(5L, "10.1.1.1", "10.1.1.20"); + } + + @Test + public void validateIsolatedNetworkDhcpRangeAcceptsRangeWithinCidr() { + // validated up front (before the network is persisted) so a bad range never leaves an orphaned network + service.validateIsolatedNetworkDhcpRange("10.1.1.10", "10.1.1.20", "10.1.1.0/24", "10.1.1.1"); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateIsolatedNetworkDhcpRangeRejectsRangeOutsideCidr() { + service.validateIsolatedNetworkDhcpRange("10.9.9.10", "10.9.9.20", "10.1.1.0/24", "10.1.1.1"); + } } diff --git a/test/integration/smoke/test_isolated_network_dhcp_range.py b/test/integration/smoke/test_isolated_network_dhcp_range.py new file mode 100644 index 000000000000..48fbf585f8c4 --- /dev/null +++ b/test/integration/smoke/test_isolated_network_dhcp_range.py @@ -0,0 +1,103 @@ +# 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. +""" Tests for a custom DHCP range on an isolated guest network +""" +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.base import (Account, + Network, + NetworkOffering, + ServiceOffering, + VirtualMachine) +from marvin.lib.common import (get_domain, + get_zone, + get_template) +from marvin.lib.utils import cleanup_resources +from nose.plugins.attrib import attr + + +class TestIsolatedNetworkDhcpRange(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestIsolatedNetworkDhcpRange, cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + cls.services = cls.testClient.getParsedTestDataConfig() + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.template = get_template(cls.apiclient, cls.zone.id, cls.services["ostype"]) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + + cls._cleanup = [] + cls.account = Account.create(cls.apiclient, cls.services["account"], admin=True, domainid=cls.domain.id) + cls._cleanup.append(cls.account) + cls.service_offering = ServiceOffering.create(cls.apiclient, cls.services["service_offerings"]["tiny"]) + cls._cleanup.append(cls.service_offering) + cls.network_offering = NetworkOffering.create(cls.apiclient, cls.services["isolated_network_offering"]) + cls.network_offering.update(cls.apiclient, state="Enabled") + cls._cleanup.append(cls.network_offering) + + @classmethod + def tearDownClass(cls): + cleanup_resources(cls.apiclient, cls._cleanup) + + @attr(tags=["advanced"], required_hardware="false") + def test_isolated_network_custom_dhcp_range(self): + """A VM on an isolated network with a custom DHCP range gets an IP within it""" + gateway = "10.99.99.1" + start_ip = "10.99.99.10" + end_ip = "10.99.99.20" + network_services = dict(self.services["network"]) + network_services["gateway"] = gateway + network_services["netmask"] = "255.255.255.0" + network_services["startip"] = start_ip + network_services["endip"] = end_ip + + network = Network.create( + self.apiclient, + network_services, + accountid=self.account.name, + domainid=self.account.domainid, + networkofferingid=self.network_offering.id, + zoneid=self.zone.id, + gateway=gateway, + netmask="255.255.255.0") + self.cleanup.append(network) + + listed = Network.list(self.apiclient, id=network.id)[0] + self.assertEqual(listed.startip, start_ip, "listNetworks should return the DHCP start IP") + self.assertEqual(listed.endip, end_ip, "listNetworks should return the DHCP end IP") + + vm = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + networkids=[network.id]) + self.cleanup.append(vm) + + octet = int(vm.nic[0].ipaddress.split(".")[-1]) + self.assertTrue( + 10 <= octet <= 20 and vm.nic[0].ipaddress.startswith("10.99.99."), + "VM IP %s is not within the custom DHCP range %s-%s" % (vm.nic[0].ipaddress, start_ip, end_ip)) + + def setUp(self): + self.cleanup = [] + + def tearDown(self): + cleanup_resources(self.apiclient, self.cleanup) diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index 1235dca8ceab..74f5c4eacc06 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -50,7 +50,7 @@ export default { return fields }, details: () => { - const fields = ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'asnumber', + const fields = ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'startip', 'endip', 'asnumber', 'aclname', 'ispersistent', 'restartrequired', 'reservediprange', 'redundantrouter', 'networkdomain', 'egressdefaultpolicy', 'zonename', 'account', 'domainpath', 'associatednetwork', 'associatednetworkid', 'ip4routing', 'ip6firewall', 'ip6routing', 'ip6routes', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu', 'privatemtu', 'dnszone', 'dnssubdomain'] diff --git a/ui/src/views/network/CreateIsolatedNetworkForm.vue b/ui/src/views/network/CreateIsolatedNetworkForm.vue index 75ac30359e98..37c8cc0a02fa 100644 --- a/ui/src/views/network/CreateIsolatedNetworkForm.vue +++ b/ui/src/views/network/CreateIsolatedNetworkForm.vue @@ -229,7 +229,7 @@ v-model:value="form.cidrsize" :placeholder="apiParams.cidrsize.description"/> - + @@ -237,7 +237,7 @@ v-model:value="form.startip" :placeholder="apiParams.startip.description"/> - +