diff --git a/api/src/main/java/com/cloud/network/VNF.java b/api/src/main/java/com/cloud/network/VNF.java index 50b316d7acb8..2c7d261e76f8 100644 --- a/api/src/main/java/com/cloud/network/VNF.java +++ b/api/src/main/java/com/cloud/network/VNF.java @@ -78,12 +78,14 @@ class VnfNic { int deviceId; String name; boolean required; + boolean management; String description; - public VnfNic(int deviceId, String nicName, boolean required, String nicDescription) { + public VnfNic(int deviceId, String nicName, boolean required, boolean management, String nicDescription) { this.deviceId = deviceId; this.name = nicName; this.required = required; + this.management = management; this.description = nicDescription; } @@ -99,6 +101,10 @@ public boolean isRequired() { return required; } + public boolean isManagement() { + return management; + } + public String getDescription() { return description; } 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 1360587f2107..614fe6076de2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1042,6 +1042,7 @@ public class ApiConstants { public static final String SOURCE_NAT_IP_ID = "sourcenatipaddressid"; public static final String HAS_RULES = "hasrules"; + public static final String MANAGEMENT = "management"; public static final String IS_VNF = "isvnf"; public static final String VNF_NICS = "vnfnics"; public static final String VNF_DETAILS = "vnfdetails"; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VnfNicResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VnfNicResponse.java index 277c09ed9ae9..82fce602cacf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VnfNicResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VnfNicResponse.java @@ -34,6 +34,10 @@ public class VnfNicResponse { @Param(description = "True if the NIC is required. False if optional") private Boolean required; + @SerializedName(ApiConstants.MANAGEMENT) + @Param(description = "True if the NIC is a management interface. False otherwise") + private Boolean management; + @SerializedName(ApiConstants.DESCRIPTION) @Param(description = "Description of the NIC") private String description; @@ -58,6 +62,10 @@ public void setRequired(Boolean required) { this.required = required; } + public void setManagement(Boolean management) { + this.management = management; + } + public void setDescription(String description) { this.description = description; } @@ -73,10 +81,11 @@ public void setNetworkName(String networkName) { public VnfNicResponse() { } - public VnfNicResponse(long deviceId, String name, Boolean required, String description) { + public VnfNicResponse(long deviceId, String name, Boolean required, Boolean management, String description) { this.deviceId = deviceId; this.name = name; this.required = required; + this.management = management; this.description = description; } } 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 0eef6536a7e7..a4d72a5a6f87 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 @@ -64,6 +64,7 @@ static List getVnfNicsList(Map vnfNics) { String deviceIdString = nicDetails.get("deviceid"); String name = nicDetails.get("name"); String requiredString = nicDetails.get("required"); + String managementString = nicDetails.get("management"); String description = nicDetails.get("description"); Integer deviceId = null; if (StringUtils.isAnyBlank(name, deviceIdString)) { @@ -75,7 +76,8 @@ static List getVnfNicsList(Map vnfNics) { throw new InvalidParameterValueException("Unable to parse VNF nic deviceId to Integer: " + deviceId); } boolean required = StringUtils.isBlank(requiredString) || Boolean.parseBoolean(requiredString); - nicsList.add(new VNF.VnfNic(deviceId, name, required, description)); + boolean management = StringUtils.isBlank(managementString) || Boolean.parseBoolean(managementString); + nicsList.add(new VNF.VnfNic(deviceId, name, required, management, description)); } } Collections.sort(nicsList, Comparator.comparing(VNF.VnfNic::getDeviceId)); diff --git a/api/src/test/java/org/apache/cloudstack/storage/template/VnfTemplateManagerTest.java b/api/src/test/java/org/apache/cloudstack/storage/template/VnfTemplateManagerTest.java index 1e2145970de8..e78a9c8ae474 100644 --- a/api/src/test/java/org/apache/cloudstack/storage/template/VnfTemplateManagerTest.java +++ b/api/src/test/java/org/apache/cloudstack/storage/template/VnfTemplateManagerTest.java @@ -108,36 +108,36 @@ public void testGetVnfNicsListWithInvalidDeviceId() { @Test public void testValidateVnfNicsAllGood() { List nicsList = new ArrayList<>(); - nicsList.add(new VnfNic(0, "eth0", true, "first NIC")); - nicsList.add(new VnfNic(1, "eth1", true, "second NIC")); - nicsList.add(new VnfNic(2, "eth2", false, "third NIC")); + nicsList.add(new VnfNic(0, "eth0", true, true, "first NIC")); + nicsList.add(new VnfNic(1, "eth1", true, true, "second NIC")); + nicsList.add(new VnfNic(2, "eth2", false, true, "third NIC")); VnfTemplateManager.validateVnfNics(nicsList); } @Test(expected = InvalidParameterValueException.class) public void testValidateVnfNicsStartWithNonzero() { List nicsList = new ArrayList<>(); - nicsList.add(new VnfNic(1, "eth0", true, "first NIC")); - nicsList.add(new VnfNic(2, "eth1", true, "second NIC")); - nicsList.add(new VnfNic(3, "eth2", false, "third NIC")); + nicsList.add(new VnfNic(1, "eth0", true, true, "first NIC")); + nicsList.add(new VnfNic(2, "eth1", true, true, "second NIC")); + nicsList.add(new VnfNic(3, "eth2", false, true, "third NIC")); VnfTemplateManager.validateVnfNics(nicsList); } @Test(expected = InvalidParameterValueException.class) public void testValidateVnfNicsWithNonConstantDeviceIds() { List nicsList = new ArrayList<>(); - nicsList.add(new VnfNic(0, "eth0", true, "first NIC")); - nicsList.add(new VnfNic(2, "eth1", true, "second NIC")); - nicsList.add(new VnfNic(4, "eth2", false, "third NIC")); + nicsList.add(new VnfNic(0, "eth0", true, true, "first NIC")); + nicsList.add(new VnfNic(2, "eth1", true, true, "second NIC")); + nicsList.add(new VnfNic(4, "eth2", false, true, "third NIC")); VnfTemplateManager.validateVnfNics(nicsList); } @Test(expected = InvalidParameterValueException.class) public void testValidateVnfNicsWithInvalidRequired() { List nicsList = new ArrayList<>(); - nicsList.add(new VnfNic(0, "eth0", true, "first NIC")); - nicsList.add(new VnfNic(1, "eth1", false, "second NIC")); - nicsList.add(new VnfNic(2, "eth2", true, "third NIC")); + nicsList.add(new VnfNic(0, "eth0", true, true, "first NIC")); + nicsList.add(new VnfNic(1, "eth1", false, true, "second NIC")); + nicsList.add(new VnfNic(2, "eth2", true, true, "third NIC")); VnfTemplateManager.validateVnfNics(nicsList); } diff --git a/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java index 4045f3817def..1f5054c0cd83 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java @@ -47,17 +47,21 @@ public class VnfTemplateNicVO implements InternalIdentity { @Column(name = "required") private boolean required = true; + @Column(name = "management") + private boolean management = true; + @Column(name = "description") private String description; public VnfTemplateNicVO() { } - public VnfTemplateNicVO(long templateId, long deviceId, String deviceName, boolean required, String description) { + public VnfTemplateNicVO(long templateId, long deviceId, String deviceName, boolean required, boolean management, String description) { this.templateId = templateId; this.deviceId = deviceId; this.deviceName = deviceName; this.required = required; + this.management = management; this.description = description; } @@ -83,10 +87,14 @@ public String getDeviceName() { return deviceName; } - public boolean getRequired() { + public boolean isRequired() { return required; } + public boolean isManagement() { + return management; + } + public String getDescription() { return description; } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql b/engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql index 0f416a9a0b78..3e011475f152 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql @@ -192,6 +192,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`vnf_template_nics` ( `device_id` bigint unsigned NOT NULL COMMENT 'Device id of the NIC when plugged into the VNF appliances', `device_name` varchar(1024) NOT NULL COMMENT 'Name of the NIC', `required` tinyint NOT NULL DEFAULT '1' COMMENT 'True if the NIC is required. False if optional', + `management` tinyint NOT NULL DEFAULT '1' COMMENT 'True if the NIC is a management interface', `description` varchar(1024) COMMENT 'Description of the NIC', PRIMARY KEY (`id`), UNIQUE KEY `uk_template_id_device_id` (`template_id`, `device_id`), diff --git a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java index 51d16a275ab4..bacb5418af09 100644 --- a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java @@ -321,7 +321,7 @@ private TemplateResponse initTemplateResponse(TemplateJoinVO template) { VnfTemplateResponse vnfTemplateResponse = new VnfTemplateResponse(); List nics = vnfTemplateNicDao.listByTemplateId(template.getId()); for (VnfTemplateNicVO nic : nics) { - vnfTemplateResponse.addVnfNic(new VnfNicResponse(nic.getDeviceId(), nic.getDeviceName(), nic.getRequired(), nic.getDescription())); + vnfTemplateResponse.addVnfNic(new VnfNicResponse(nic.getDeviceId(), nic.getDeviceName(), nic.isRequired(), nic.isManagement(), nic.getDescription())); } List details = vnfTemplateDetailsDao.listDetails(template.getId()); Collections.sort(details, (v1, v2) -> v1.getName().compareToIgnoreCase(v2.getName())); diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index 9f64dd36f751..e67b79605351 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -433,7 +433,7 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us if (TemplateType.VNF.equals(userVm.getTemplateType()) && (details.contains(VMDetails.all) || details.contains(VMDetails.vnfnics))) { List vnfNics = vnfTemplateNicDao.listByTemplateId(userVm.getTemplateId()); for (VnfTemplateNicVO nic : vnfNics) { - userVmResponse.addVnfNic(new VnfNicResponse(nic.getDeviceId(), nic.getDeviceName(), nic.getRequired(), nic.getDescription())); + userVmResponse.addVnfNic(new VnfNicResponse(nic.getDeviceId(), nic.getDeviceName(), nic.isRequired(), nic.isManagement(), nic.getDescription())); } List vnfDetails = vnfTemplateDetailsDao.listDetails(userVm.getTemplateId()); Collections.sort(vnfDetails, (v1, v2) -> v1.getName().compareToIgnoreCase(v2.getName())); 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 debf762c0822..a3b5d0d44e8f 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 @@ -83,7 +83,7 @@ public void persistVnfTemplate(long templateId, RegisterVnfTemplateCmd cmd) { private void persistVnfTemplateNics(long templateId, List nics) { for (VNF.VnfNic nic : nics) { - VnfTemplateNicVO vnfTemplateNicVO = new VnfTemplateNicVO(templateId, nic.getDeviceId(), nic.getName(), nic.isRequired(), nic.getDescription()); + VnfTemplateNicVO vnfTemplateNicVO = new VnfTemplateNicVO(templateId, nic.getDeviceId(), nic.getName(), nic.isRequired(), nic.isManagement(), nic.getDescription()); vnfTemplateNicDao.persist(vnfTemplateNicVO); } } @@ -144,7 +144,7 @@ public void validateVnfApplianceNics(VirtualMachineTemplate template, List } List vnfNics = vnfTemplateNicDao.listByTemplateId(template.getId()); for (VnfTemplateNicVO vnfNic : vnfNics) { - if (vnfNic.getRequired() && networkIds.size() <= vnfNic.getDeviceId()) { + if (vnfNic.isRequired() && networkIds.size() <= vnfNic.getDeviceId()) { throw new InvalidParameterValueException("VNF nic is required but not found: " + vnfNic); } } diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 739775da4e5f..d134154d7e50 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -6817,4 +6817,3 @@ def list(cls, apiclient, **kwargs): if 'account' in list(kwargs.keys()) and 'domainid' in list(kwargs.keys()): cmd.listall = True return (apiclient.listVnfTemplates(cmd)) - diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index b25d681d6abd..9f048b71f91f 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2177,7 +2177,7 @@ "label.vmwarenetworklabel": "VMware traffic label", "label.vnf.appliance": "VNF Appliance", "label.vnf.appliance.add": "Add VNF Appliance", -"label.vnf.appliance.access.methods": "Access information of VNF appliance", +"label.vnf.appliance.access.methods": "Access information of this VNF appliance", "label.vnf.detail.add": "Add VNF detail", "label.vnf.detail.remove": "Remove VNF detail", "label.vnf.details": "VNF details", @@ -2186,9 +2186,11 @@ "label.vnf.nic.description": "Description of VNF nic", "label.vnf.nic.deviceid": "Device ID of VNF nic. It starts with 0", "label.vnf.nic.edit": "Edit VNF nic", +"label.vnf.nic.management": "Management NIC", +"label.vnf.nic.management.description": "True if the VNF nic is a management interface. False otherwise", "label.vnf.nic.name": "Name of VNF nic", "label.vnf.nic.remove": "Remove VNF nic", -"label.vnf.nic.required": "True of VNF nic is required. Otherwise optional", +"label.vnf.nic.required": "True if VNF nic is required. Otherwise optional", "label.vnf.nics": "VNF nics", "label.vnf.settings": "VNF settings", "label.vnf.templates": "VNF templates", diff --git a/ui/src/components/view/DetailsTab.vue b/ui/src/components/view/DetailsTab.vue index 205db670c068..1d0f2cf8c7a9 100644 --- a/ui/src/components/view/DetailsTab.vue +++ b/ui/src/components/view/DetailsTab.vue @@ -187,18 +187,35 @@ export default { if (password) { credentials.push(this.$t('label.password') + ' : ' + password) } + if (webUsername) { + credentials.push('Web ' + this.$t('label.username') + ' : ' + webUsername) + } + if (webPassword) { + credentials.push('Web ' + this.$t('label.password') + ' : ' + webPassword) + } if (sshUsername) { credentials.push('SSH ' + this.$t('label.username') + ' : ' + sshUsername) } if (sshPassword) { credentials.push('SSH ' + this.$t('label.password') + ' : ' + sshPassword) } - if (webUsername) { - credentials.push('Web ' + this.$t('label.username') + ' : ' + webUsername) + + const managementDeviceIds = [] + for (const vnfnic of this.resource.vnfnics) { + if (vnfnic.management) { + managementDeviceIds.push(vnfnic.deviceid) + } } - if (webPassword) { - credentials.push('Web ' + this.$t('label.password') + ' : ' + webPassword) + const managementIps = [] + for (const nic of this.resource.nic) { + if (managementDeviceIds.includes(parseInt(nic.deviceid)) && nic.ipaddress) { + managementIps.push(nic.ipaddress) + } + } + if (this.resource.publicip && managementDeviceIds.includes(0)) { + managementIps.push(this.resource.publicip) } + console.log('managementIps = ' + managementIps) if (accessMethods) { const accessMethodsArray = accessMethods.split(',') @@ -210,14 +227,12 @@ export default { } else if (accessMethod === 'ssh-key') { accessMethodsDescription.push('- SSH with key' + (sshPort ? ' (SSH port is ' + sshPort + ').' : '.')) } else if (accessMethod === 'http') { - accessMethodsDescription.push('- Webpage: http://' + this.resource.ipaddress + (httpPort ? ':' + httpPort : '') + (httpPath ? '/' + httpPath : '')) - if (this.resource.publicip) { - accessMethodsDescription.push('- Webpage: http://' + this.resource.publicip + (httpPort ? ':' + httpPort : '') + (httpPath ? '/' + httpPath : '')) + for (const managementIp of managementIps) { + accessMethodsDescription.push('- Webpage: http://' + managementIp + (httpPort ? ':' + httpPort : '') + (httpPath ? '/' + httpPath : '')) } } else if (accessMethod === 'https') { - accessMethodsDescription.push('- Webpage: https://' + this.resource.ipaddress + (httpsPort ? ':' + httpsPort : '') + (httpsPath ? '/' + httpsPath : '')) - if (this.resource.publicip) { - accessMethodsDescription.push('- Webpage: https://' + this.resource.publicip + (httpsPort ? ':' + httpsPort : '') + (httpsPath ? '/' + httpsPath : '')) + for (const managementIp of managementIps) { + accessMethodsDescription.push('- Webpage: https://' + managementIp + (httpsPort ? ':' + httpsPort : '') + (httpsPath ? '/' + httpsPath : '')) } } } diff --git a/ui/src/views/image/TemplateVnfSettings.vue b/ui/src/views/image/TemplateVnfSettings.vue index 7cfa6d425174..f1d2230ee0f8 100644 --- a/ui/src/views/image/TemplateVnfSettings.vue +++ b/ui/src/views/image/TemplateVnfSettings.vue @@ -53,6 +53,10 @@ {{ $t('label.yes') }} {{ $t('label.no') }} + @@ -122,6 +126,13 @@ +
+
+ * + +
+ +
@@ -166,6 +177,13 @@
+
+
+ * + +
+ +
@@ -210,6 +228,13 @@
+
+
+ * + +
+ +
@@ -391,12 +416,14 @@ export default { deviceid: null, name: null, required: true, + management: false, description: null }, updateVnfNic: { deviceid: null, name: null, required: true, + management: false, description: null }, vnfDetails: [], @@ -449,6 +476,11 @@ export default { dataIndex: 'required', slots: { customRender: 'required' } }, + { + title: this.$t('label.vnf.nic.management'), + dataIndex: 'management', + slots: { customRender: 'management' } + }, { title: this.$t('label.description'), dataIndex: 'description', @@ -533,6 +565,7 @@ export default { deviceid: this.newVnfNic.deviceid, name: this.newVnfNic.name, required: this.newVnfNic.required, + management: this.newVnfNic.management, description: this.newVnfNic.description }) } @@ -552,6 +585,7 @@ export default { this.updateVnfNic.deviceid = record.deviceid this.updateVnfNic.name = record.name this.updateVnfNic.required = record.required + this.updateVnfNic.management = record.management this.updateVnfNic.description = record.description this.showEditVnfNic = true }, @@ -670,6 +704,7 @@ export default { params['vnfnics[' + i + '].deviceid'] = nic.deviceid params['vnfnics[' + i + '].name'] = nic.name params['vnfnics[' + i + '].required'] = nic.required + params['vnfnics[' + i + '].management'] = nic.management params['vnfnics[' + i + '].description'] = nic.description i++ } @@ -747,6 +782,7 @@ export default { deviceid: null, name: null, required: true, + management: false, description: null } }