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 7d85d16ccf20..2a151a4f7810 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1048,6 +1048,8 @@ public class ApiConstants { public static final String VNF_DETAILS = "vnfdetails"; public static final String CLEAN_UP_VNF_DETAILS = "cleanupvnfdetails"; public static final String CLEAN_UP_VNF_NICS = "cleanupvnfnics"; + public static final String VNF_CONFIGURE_MANAGEMENT = "vnfconfiguremanagement"; + public static final String VNF_CIDR_LIST = "vnfcidrlist"; /** * This enum specifies IO Drivers, each option controls specific policies on I/O. diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVnfApplianceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVnfApplianceCmd.java index e6fa86d9d758..0067a16405c9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVnfApplianceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVnfApplianceCmd.java @@ -16,12 +16,20 @@ // under the License. package org.apache.cloudstack.api.command.user.vm; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.utils.net.NetUtils; import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.storage.template.VnfTemplateUtils; + +import java.util.ArrayList; +import java.util.List; @APICommand(name = "deployVnfAppliance", description = "Creates and automatically starts a VNF appliance based on a service offering, disk offering, and template.", @@ -32,4 +40,33 @@ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, since = "4.19.0") public class DeployVnfApplianceCmd extends DeployVMCmd implements UserCmd { + + @Parameter(name = ApiConstants.VNF_CONFIGURE_MANAGEMENT, type = CommandType.BOOLEAN, required = false, + description = "True by default, security group or network rules (source nat and firewall rules) will be configured for VNF management interfaces. False otherwise.") + private Boolean vnfConfigureManagement; + + @Parameter(name = ApiConstants.VNF_CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, + description = "the CIDR list to forward traffic from to the VNF management interface. Multiple entries must be separated by a single comma character (,).") + private List vnfCidrlist; + + public Boolean getVnfConfigureManagement() { + return vnfConfigureManagement == null || vnfConfigureManagement; + } + + public List getVnfCidrlist() { + if (vnfCidrlist != null) { + return vnfCidrlist; + } else { + List defaultCidrList = new ArrayList(); + defaultCidrList.add(NetUtils.ALL_IP4_CIDRS); + return defaultCidrList; + } + } + + @Override + public void create() throws ResourceAllocationException { + VnfTemplateUtils.validateVnfCidrList(this.getVnfCidrlist()); + + super.create(); + } } diff --git a/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManager.java b/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManager.java index 49017ee462a1..6571346ad654 100644 --- a/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManager.java +++ b/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManager.java @@ -16,9 +16,17 @@ // under the License. package org.apache.cloudstack.storage.template; +import com.cloud.dc.DataCenter; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.security.SecurityGroup; import com.cloud.template.VirtualMachineTemplate; +import com.cloud.user.Account; +import com.cloud.uservm.UserVm; import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd; import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd; +import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd; import org.apache.cloudstack.framework.config.ConfigKey; import java.util.List; @@ -35,4 +43,10 @@ public interface VnfTemplateManager { void updateVnfTemplate(long templateId, UpdateVnfTemplateCmd cmd); void validateVnfApplianceNics(VirtualMachineTemplate template, List networkIds); + + SecurityGroup createSecurityGroupForVnfAppliance(DataCenter zone, VirtualMachineTemplate template, Account owner, DeployVnfApplianceCmd cmd); + + void createIsolatedNetworkRulesForVnfAppliance(DataCenter zone, VirtualMachineTemplate template, Account owner, + UserVm vm, DeployVnfApplianceCmd cmd) + throws InsufficientAddressCapacityException, ResourceAllocationException, ResourceUnavailableException; } diff --git a/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateUtils.java b/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateUtils.java index 9d178089c6f4..62109b18e023 100644 --- a/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateUtils.java +++ b/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateUtils.java @@ -20,10 +20,12 @@ import com.cloud.network.VNF; import com.cloud.storage.Storage; import com.cloud.template.VirtualMachineTemplate; +import com.cloud.utils.net.NetUtils; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.command.user.template.DeleteVnfTemplateCmd; import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd; import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; @@ -109,7 +111,7 @@ public static void validateVnfNics(List nicsList) { throw new InvalidParameterValueException(String.format("deviceid must be consecutive and start from 0. Nic deviceid should be %s but actual is %s.", deviceId, nic.getDeviceId())); } if (!required && nic.isRequired()) { - throw new InvalidParameterValueException(String.format("required cannot be true if a precedent nic is optional. Nic with deviceid %s should be required but actual is optional.", deviceId)); + throw new InvalidParameterValueException(String.format("required cannot be true if a preceding nic is optional. Nic with deviceid %s should be required but actual is optional.", deviceId)); } deviceId ++; required = nic.isRequired(); @@ -133,4 +135,15 @@ public static void validateApiCommandParams(BaseCmd cmd, VirtualMachineTemplate } } } + + public static void validateVnfCidrList(List cidrList) { + if (CollectionUtils.isEmpty(cidrList)) { + return; + } + for (String cidr : cidrList) { + if (!NetUtils.isValidIp4Cidr(cidr)) { + throw new InvalidParameterValueException(String.format("Invalid cidr for VNF appliance: %s", cidr)); + } + } + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/NicVO.java b/engine/schema/src/main/java/com/cloud/vm/NicVO.java index fba7c966c442..a32a943ea585 100644 --- a/engine/schema/src/main/java/com/cloud/vm/NicVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/NicVO.java @@ -333,6 +333,8 @@ public String toString() { .append("-") .append(instanceId) .append("-") + .append(deviceId) + .append("-") .append(reservationId) .append("-") .append(iPv4Address) diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 050d4168f2bb..eb6adfb5fb5d 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -69,6 +69,7 @@ import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; import org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd; import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; +import org.apache.cloudstack.api.command.user.vm.DeployVnfApplianceCmd; import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd; import org.apache.cloudstack.api.command.user.vm.RebootVMCmd; import org.apache.cloudstack.api.command.user.vm.RemoveNicFromVMCmd; @@ -5863,6 +5864,8 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE } if (TemplateType.VNF.equals(template.getTemplateType())) { vnfTemplateManager.validateVnfApplianceNics(template, cmd.getNetworkIds()); + } else if (cmd instanceof DeployVnfApplianceCmd) { + throw new InvalidParameterValueException("Can't deploy VNF appliance from a non-VNF template"); } ServiceOfferingJoinVO svcOffering = serviceOfferingJoinDao.findById(serviceOfferingId); @@ -5945,14 +5948,14 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE if (networkIds != null) { throw new InvalidParameterValueException("Can't specify network Ids in Basic zone"); } else { - vm = createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(cmd), owner, name, displayName, diskOfferingId, + vm = createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(cmd, zone, template, owner), owner, name, displayName, diskOfferingId, size , group , cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm , keyboard , cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId); } } else { if (zone.isSecurityGroupEnabled()) { - vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, getSecurityGroupIdList(cmd), owner, name, + vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, getSecurityGroupIdList(cmd, zone, template, owner), owner, name, displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId, null); @@ -5964,6 +5967,9 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE vm = createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, name, displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, userDataId, userDataDetails, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId); + if (cmd instanceof DeployVnfApplianceCmd) { + vnfTemplateManager.createIsolatedNetworkRulesForVnfAppliance(zone, template, owner, vm, (DeployVnfApplianceCmd) cmd); + } } } @@ -6230,6 +6236,17 @@ protected List getSecurityGroupIdList(SecurityGroupAction cmd) { } } + protected List getSecurityGroupIdList(SecurityGroupAction cmd, DataCenter zone, VirtualMachineTemplate template, Account owner) { + List securityGroupIdList = getSecurityGroupIdList(cmd); + if (cmd instanceof DeployVnfApplianceCmd) { + SecurityGroup securityGroup = vnfTemplateManager.createSecurityGroupForVnfAppliance(zone, template, owner, (DeployVnfApplianceCmd) cmd); + if (securityGroup != null) { + securityGroupIdList.add(securityGroup.getId()); + } + } + return securityGroupIdList; + } + // this is an opportunity to verify that parameters that came in via the Details Map are OK // for example, minIops and maxIops should either both be specified or neither be specified and, // if specified, minIops should be <= maxIops diff --git a/server/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManagerImpl.java b/server/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManagerImpl.java index 3f88d34b603f..ca6d54b59af1 100644 --- a/server/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManagerImpl.java @@ -18,20 +18,56 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; +import com.cloud.dc.DataCenter; +import com.cloud.exception.InsufficientAddressCapacityException; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.IpAddress; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkService; import com.cloud.network.VNF; +import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.firewall.FirewallService; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.FirewallRuleVO; +import com.cloud.network.rules.RulesService; +import com.cloud.network.security.SecurityGroup; +import com.cloud.network.security.SecurityGroupManager; +import com.cloud.network.security.SecurityGroupService; +import com.cloud.network.security.SecurityGroupVO; +import com.cloud.network.security.SecurityRule; +import com.cloud.network.vpc.VpcService; +import com.cloud.storage.VnfTemplateDetailVO; import com.cloud.storage.VnfTemplateNicVO; import com.cloud.storage.dao.VnfTemplateDetailsDao; import com.cloud.storage.dao.VnfTemplateNicDao; import com.cloud.template.VirtualMachineTemplate; +import com.cloud.user.Account; +import com.cloud.uservm.UserVm; +import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.PluggableService; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.NicVO; +import com.cloud.vm.dao.NicDao; import org.apache.cloudstack.api.command.admin.template.ListVnfTemplatesCmdByAdmin; import org.apache.cloudstack.api.command.admin.template.RegisterVnfTemplateCmdByAdmin; import org.apache.cloudstack.api.command.admin.template.UpdateVnfTemplateCmdByAdmin; @@ -52,10 +88,38 @@ public class VnfTemplateManagerImpl extends ManagerBase implements VnfTemplateMa static final Logger LOGGER = Logger.getLogger(VnfTemplateManagerImpl.class); + public static final String VNF_SECURITY_GROUP_NAME = "VNF_SecurityGroup_"; + public static final String ACCESS_METHOD_SEPARATOR = ","; + public static final Integer ACCESS_DEFAULT_SSH_PORT = 22; + public static final Integer ACCESS_DEFAULT_HTTP_PORT = 80; + public static final Integer ACCESS_DEFAULT_HTTPS_PORT = 443; + @Inject VnfTemplateDetailsDao vnfTemplateDetailsDao; @Inject VnfTemplateNicDao vnfTemplateNicDao; + @Inject + SecurityGroupManager securityGroupManager; + @Inject + SecurityGroupService securityGroupService; + @Inject + NetworkModel networkModel; + @Inject + IpAddressManager ipAddressManager; + @Inject + NicDao nicDao; + @Inject + NetworkDao networkDao; + @Inject + NetworkService networkService; + @Inject + VpcService vpcService; + @Inject + RulesService rulesService; + @Inject + FirewallRulesDao firewallRulesDao; + @Inject + FirewallService firewallService; @Override public List> getCommands() { @@ -96,7 +160,7 @@ private void persistVnfTemplateDetails(long templateId, Map vnfD for (Map.Entry entry: vnfDetails.entrySet()) { String value = entry.getValue(); if (VNF.AccessDetail.ACCESS_METHODS.name().equalsIgnoreCase(entry.getKey())) { - value = Arrays.stream(value.split(",")).sorted().collect(Collectors.joining(",")); + value = Arrays.stream(value.split(ACCESS_METHOD_SEPARATOR)).sorted().collect(Collectors.joining(ACCESS_METHOD_SEPARATOR)); } vnfTemplateDetailsDao.addDetail(templateId, entry.getKey().toLowerCase(), value, true); } @@ -150,4 +214,150 @@ public void validateVnfApplianceNics(VirtualMachineTemplate template, List } } } + + private Set getOpenPortsForVnfAppliance(VirtualMachineTemplate template) { + Set ports = new HashSet<>(); + VnfTemplateDetailVO accessMethodsDetail = vnfTemplateDetailsDao.findDetail(template.getId(), VNF.AccessDetail.ACCESS_METHODS.name().toLowerCase()); + if (accessMethodsDetail == null || accessMethodsDetail.getValue() == null) { + return ports; + } + String[] accessMethods = accessMethodsDetail.getValue().split(ACCESS_METHOD_SEPARATOR); + for (String accessMethod : accessMethods) { + if (VNF.AccessMethod.SSH_WITH_KEY.toString().equalsIgnoreCase(accessMethod) + || VNF.AccessMethod.SSH_WITH_PASSWORD.toString().equalsIgnoreCase(accessMethod)) { + VnfTemplateDetailVO accessDetail = vnfTemplateDetailsDao.findDetail(template.getId(), VNF.AccessDetail.SSH_PORT.name().toLowerCase()); + if (accessDetail == null) { + ports.add(ACCESS_DEFAULT_SSH_PORT); + } else { + ports.add(NumbersUtil.parseInt(accessDetail.getValue(), ACCESS_DEFAULT_SSH_PORT)); + } + } else if (VNF.AccessMethod.HTTP.toString().equalsIgnoreCase(accessMethod)) { + VnfTemplateDetailVO accessDetail = vnfTemplateDetailsDao.findDetail(template.getId(), VNF.AccessDetail.HTTP_PORT.name().toLowerCase()); + if (accessDetail == null) { + ports.add(ACCESS_DEFAULT_HTTP_PORT); + } else { + ports.add(NumbersUtil.parseInt(accessDetail.getValue(), ACCESS_DEFAULT_HTTP_PORT)); + } + } else if (VNF.AccessMethod.HTTPS.toString().equalsIgnoreCase(accessMethod)) { + VnfTemplateDetailVO accessDetail = vnfTemplateDetailsDao.findDetail(template.getId(), VNF.AccessDetail.HTTPS_PORT.name().toLowerCase()); + if (accessDetail == null) { + ports.add(ACCESS_DEFAULT_HTTPS_PORT); + } else { + ports.add(NumbersUtil.parseInt(accessDetail.getValue(), ACCESS_DEFAULT_HTTPS_PORT)); + } + } + } + return ports; + } + + private Set getDeviceIdsOfVnfManagementNics(VirtualMachineTemplate template) { + Set deviceIds = new HashSet<>(); + for (VnfTemplateNicVO nic : vnfTemplateNicDao.listByTemplateId(template.getId())) { + if (nic.isManagement()) { + deviceIds.add(nic.getDeviceId()); + } + } + return deviceIds; + } + + private Map getManagementNetworkAndIp(VirtualMachineTemplate template, UserVm vm) { + Map networkAndIpMap = new HashMap<>(); + Set managementDeviceIds = getDeviceIdsOfVnfManagementNics(template); + for (NicVO nic : nicDao.listByVmId(vm.getId())) { + if (managementDeviceIds.contains((long) nic.getDeviceId()) && nic.getIPv4Address() != null) { + Network network = networkDao.findById(nic.getNetworkId()); + if (network == null || !Network.GuestType.Isolated.equals(network.getGuestType())) { + continue; + } + if (!networkModel.areServicesSupportedInNetwork(network.getId(), Network.Service.StaticNat)) { + LOGGER.info(String.format("Network ID: %s does not support static nat, " + + "skipping this network configuration for VNF appliance", network.getUuid())); + continue; + } + if (network.getVpcId() == null && !networkModel.areServicesSupportedInNetwork(network.getId(), Network.Service.Firewall)) { + LOGGER.info(String.format("Network ID: %s does not support firewall, " + + "skipping this network configuration for VNF appliance", network.getUuid())); + continue; + } + networkAndIpMap.put(network, nic.getIPv4Address()); + } + } + return networkAndIpMap; + } + + @Override + public SecurityGroup createSecurityGroupForVnfAppliance(DataCenter zone, VirtualMachineTemplate template, Account owner, + DeployVnfApplianceCmd cmd) { + if (zone == null || !zone.isSecurityGroupEnabled()) { + return null; + } + if (!cmd.getVnfConfigureManagement()) { + return null; + } + LOGGER.debug("Creating security group and rules for VNF appliance"); + Set ports = getOpenPortsForVnfAppliance(template); + String securityGroupName = VNF_SECURITY_GROUP_NAME.concat(Long.toHexString(System.currentTimeMillis())); + SecurityGroupVO securityGroupVO = securityGroupManager.createSecurityGroup(securityGroupName, + "Security group for VNF appliance", owner.getDomainId(), owner.getId(), owner.getAccountName()); + if (securityGroupVO == null) { + throw new CloudRuntimeException(String.format("Failed to create security group: %s", securityGroupName)); + } + List cidrList = cmd.getVnfCidrlist(); + for (Integer port : ports) { + securityGroupService.authorizeSecurityGroupRule(securityGroupVO.getId(), NetUtils.TCP_PROTO, port, port, + null, null, cidrList, null, SecurityRule.SecurityRuleType.IngressRule); + } + securityGroupService.authorizeSecurityGroupRule(securityGroupVO.getId(), NetUtils.ALL_PROTO, + null, null, null, null, cidrList, null, SecurityRule.SecurityRuleType.EgressRule); + return securityGroupVO; + } + + @Override + public void createIsolatedNetworkRulesForVnfAppliance(DataCenter zone, VirtualMachineTemplate template, Account owner, + UserVm vm, DeployVnfApplianceCmd cmd) + throws InsufficientAddressCapacityException, ResourceAllocationException, ResourceUnavailableException { + + Map networkAndIpMap = getManagementNetworkAndIp(template, vm); + Set ports = getOpenPortsForVnfAppliance(template); + for (Map.Entry entry : networkAndIpMap.entrySet()) { + Network network = entry.getKey(); + LOGGER.debug("Creating network rules for VNF appliance on isolated network " + network.getUuid()); + String ip = entry.getValue(); + IpAddress publicIp = networkService.allocateIP(owner, zone.getId(), network.getId(), null, null); + if (publicIp == null) { + continue; + } + if (network.getVpcId() != null) { + publicIp = vpcService.associateIPToVpc(publicIp.getId(), network.getVpcId()); + } + publicIp = ipAddressManager.associateIPToGuestNetwork(publicIp.getId(), network.getId(), false); + final IpAddress publicIpFinal = publicIp; + final List cidrList = cmd.getVnfCidrlist(); + try { + boolean result = rulesService.enableStaticNat(publicIp.getId(), vm.getId(), network.getId(), ip); + if (!result) { + throw new CloudRuntimeException(String.format("Failed to create static nat for vm: %s", vm.getUuid())); + } + } catch (NetworkRuleConflictException e) { + throw new CloudRuntimeException(String.format("Failed to create static nat for vm %s due to %s", vm.getUuid(), e.getMessage())); + } + if (network.getVpcId() == null) { + Transaction.execute(new TransactionCallbackWithExceptionNoReturn<>() { + @Override + public void doInTransactionWithoutResult(final TransactionStatus status) throws CloudRuntimeException { + for (Integer port : ports) { + FirewallRuleVO newFirewallRule = new FirewallRuleVO(null, publicIpFinal.getId(), port, port, NetUtils.TCP_PROTO, + network.getId(), owner.getAccountId(), owner.getDomainId(), FirewallRule.Purpose.Firewall, + cidrList, null, null, null, FirewallRule.TrafficType.Ingress); + newFirewallRule.setDisplay(true); + newFirewallRule.setState(FirewallRule.State.Add); + firewallRulesDao.persist(newFirewallRule); + } + } + }); + firewallService.applyIngressFwRules(publicIp.getId(), owner); + } + LOGGER.debug("Created network rules for VNF appliance on isolated network " + network.getUuid()); + } + } } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index a4b344f44a4a..857588fb1186 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2182,6 +2182,10 @@ "label.vnf.appliances": "VNF appliances", "label.vnf.appliance.add": "Add VNF Appliance", "label.vnf.appliance.access.methods": "Management access information of this VNF appliance", +"label.vnf.cidr.list": "Source cidr list of rules", +"label.vnf.cidr.list.tooltip": "the CIDR list to forward traffic from to the VNF management interface. Multiple entries must be separated by a single comma character (,).", +"label.vnf.configure.management": "Configure rules for VNF management interfaces", +"label.vnf.configure.management.tooltip": "True by default, security group or network rules (source nat and firewall rules) will be configured for VNF management interfaces. False otherwise.", "label.vnf.detail.add": "Add VNF detail", "label.vnf.detail.remove": "Remove VNF detail", "label.vnf.details": "VNF details", diff --git a/ui/src/views/compute/DeployVnfAppliance.vue b/ui/src/views/compute/DeployVnfAppliance.vue index 701fb9bef697..6b0e68402646 100644 --- a/ui/src/views/compute/DeployVnfAppliance.vue +++ b/ui/src/views/compute/DeployVnfAppliance.vue @@ -769,6 +769,18 @@ :filterOption="filterOption" > + + + + + + + + @@ -1516,6 +1528,7 @@ export default { if (this.zoneSelected) { this.form.startvm = true + this.form.vnfconfiguremanagement = true } if (this.zone && this.zone.networktype !== 'Basic') { @@ -2084,6 +2097,8 @@ export default { } createVnfAppData.startvm = values.startvm + createVnfAppData.vnfconfiguremanagement = values.vnfconfiguremanagement + createVnfAppData.vnfcidrlist = values.vnfcidrlist // step 3: select service offering createVnfAppData.serviceofferingid = values.computeofferingid @@ -2496,6 +2511,8 @@ export default { this.zone = _.find(this.options.zones, (option) => option.id === value) this.zoneSelected = true this.form.startvm = true + this.form.vnfconfiguremanagement = true + this.form.vnfcidrlist = '' this.selectedZone = this.zoneId this.form.zoneid = this.zoneId this.form.clusterid = undefined