diff --git a/api/src/main/java/com/cloud/network/VNF.java b/api/src/main/java/com/cloud/network/VNF.java new file mode 100644 index 000000000000..50b316d7acb8 --- /dev/null +++ b/api/src/main/java/com/cloud/network/VNF.java @@ -0,0 +1,106 @@ +// 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.commons.lang3.StringUtils; + +public interface VNF { + + enum AccessMethod { + SSH_WITH_PASSWORD("ssh-password"), + SSH_WITH_KEY("ssh-key"), + HTTP("http"), + HTTPS("https"), + CONSOLE("console"); + + String _method; + + AccessMethod(String method) { + _method = method; + } + + @Override + public String toString() { + return _method; + } + + public static AccessMethod fromValue(String method) { + if (StringUtils.isBlank(method)) { + return null; + } else { + for (AccessMethod accessMethod : AccessMethod.values()) { + if (accessMethod.toString().equalsIgnoreCase(method)) { + return accessMethod; + } + } + } + return null; + } + } + + enum AccessDetail { + ACCESS_METHODS, + USERNAME, + PASSWORD, + SSH_USER, + SSH_PASSWORD, + SSH_PORT, + WEB_USER, + WEB_PASSWORD, + HTTP_PATH, + HTTP_PORT, + HTTPS_PATH, + HTTPS_PORT + } + + enum VnfDetail { + ICON, + VERSION, + VENDOR, + MAINTAINER + } + + class VnfNic { + int deviceId; + String name; + boolean required; + String description; + + public VnfNic(int deviceId, String nicName, boolean required, String nicDescription) { + this.deviceId = deviceId; + this.name = nicName; + this.required = required; + this.description = nicDescription; + } + + public int getDeviceId() { + return deviceId; + } + + public String getName() { + return name; + } + + public boolean isRequired() { + return required; + } + + public String getDescription() { + return description; + } + } +} diff --git a/api/src/main/java/com/cloud/server/ResourceTag.java b/api/src/main/java/com/cloud/server/ResourceTag.java index 6288446cbdda..89ec5b905c97 100644 --- a/api/src/main/java/com/cloud/server/ResourceTag.java +++ b/api/src/main/java/com/cloud/server/ResourceTag.java @@ -30,6 +30,7 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit public enum ResourceObjectType { UserVm(true, true, true), Template(true, true, true), + VnfTemplate(true, true, true), ISO(true, false, true), Volume(true, true), Snapshot(true, false), diff --git a/api/src/main/java/com/cloud/storage/Storage.java b/api/src/main/java/com/cloud/storage/Storage.java index c6dee56fa228..1ee7200a313e 100644 --- a/api/src/main/java/com/cloud/storage/Storage.java +++ b/api/src/main/java/com/cloud/storage/Storage.java @@ -125,6 +125,7 @@ public static enum TemplateType { BUILTIN, /* buildin template */ PERHOST, /* every host has this template, don't need to install it in secondary storage */ USER, /* User supplied template/iso */ + VNF, /* VNFs (virtual network functions) template */ DATADISK, /* Template corresponding to a datadisk(non root disk) present in an OVA */ ISODISK /* Template corresponding to a iso (non root disk) present in an OVA */ } 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 3e0e65220e17..1360587f2107 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -433,6 +433,7 @@ 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 TEMPLATE_TYPE = "templatetype"; public static final String TIMEOUT = "timeout"; public static final String TIMEZONE = "timezone"; public static final String TIMEZONEOFFSET = "timezoneoffset"; @@ -1008,7 +1009,6 @@ public class ApiConstants { public static final String DEPLOY_AS_IS = "deployasis"; public static final String DEPLOY_AS_IS_DETAILS = "deployasisdetails"; public static final String CROSS_ZONES = "crossZones"; - public static final String TEMPLATETYPE = "templatetype"; public static final String SOURCETEMPLATEID = "sourcetemplateid"; public static final String DYNAMIC_SCALING_ENABLED = "dynamicscalingenabled"; public static final String IOTHREADS_ENABLED = "iothreadsenabled"; @@ -1042,6 +1042,11 @@ public class ApiConstants { public static final String SOURCE_NAT_IP_ID = "sourcenatipaddressid"; public static final String HAS_RULES = "hasrules"; + public static final String IS_VNF = "isvnf"; + public static final String VNF_NICS = "vnfnics"; + public static final String VNF_DETAILS = "vnfdetails"; + public static final String CLEAN_UP_VNF_DETAILS = "cleanupvnfdetails"; + /** * This enum specifies IO Drivers, each option controls specific policies on I/O. * Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0). @@ -1087,7 +1092,7 @@ public enum HostDetails { } public enum VMDetails { - all, group, nics, stats, secgrp, tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp; + all, group, nics, stats, secgrp, tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp, vnfnics; } public enum DomainDetails { 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 79e103a291dc..0b80cfc82295 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java @@ -43,6 +43,7 @@ import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService; import org.apache.cloudstack.query.QueryService; import org.apache.cloudstack.storage.ImageStoreService; +import org.apache.cloudstack.storage.template.VnfTemplateManager; import org.apache.cloudstack.usage.UsageService; import org.apache.commons.collections.MapUtils; import org.apache.log4j.Logger; @@ -213,6 +214,8 @@ public static enum CommandType { public ResourceIconManager resourceIconManager; @Inject public Ipv6Service ipv6Service; + @Inject + public VnfTemplateManager vnfTemplateManager; public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListVnfTemplatesCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListVnfTemplatesCmdByAdmin.java new file mode 100644 index 000000000000..3dc57e03d497 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListVnfTemplatesCmdByAdmin.java @@ -0,0 +1,31 @@ +// 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.template; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.command.user.template.ListVnfTemplatesCmd; +import org.apache.cloudstack.api.response.TemplateResponse; + +import com.cloud.template.VirtualMachineTemplate; + +@APICommand(name = "listVnfTemplates", description = "List all public, private, and privileged VNF templates.", + responseObject = TemplateResponse.class, entityType = {VirtualMachineTemplate.class}, responseView = ResponseView.Full, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListVnfTemplatesCmdByAdmin extends ListVnfTemplatesCmd implements AdminCmd { +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterTemplateCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterTemplateCmdByAdmin.java index 28593755c115..91c0dd50e8e3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterTemplateCmdByAdmin.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterTemplateCmdByAdmin.java @@ -18,9 +18,11 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.command.admin.AdminCmd; import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; import org.apache.cloudstack.api.response.TemplateResponse; @APICommand(name = "registerTemplate", description = "Registers an existing template into the CloudStack cloud.", responseObject = TemplateResponse.class, responseView = ResponseView.Full, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class RegisterTemplateCmdByAdmin extends RegisterTemplateCmd {} +public class RegisterTemplateCmdByAdmin extends RegisterTemplateCmd implements AdminCmd { +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterVnfTemplateCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterVnfTemplateCmdByAdmin.java new file mode 100644 index 000000000000..822d2e223129 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/RegisterVnfTemplateCmdByAdmin.java @@ -0,0 +1,30 @@ +// 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.template; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd; +import org.apache.cloudstack.api.response.TemplateResponse; + +@APICommand(name = "registerVnfTemplate", + description = "Registers an existing VNF template into the CloudStack cloud. ", + responseObject = TemplateResponse.class, responseView = ResponseView.Full, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class RegisterVnfTemplateCmdByAdmin extends RegisterVnfTemplateCmd implements AdminCmd { +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateTemplateCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateTemplateCmdByAdmin.java index 09591c809b13..b1dfae3ed83f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateTemplateCmdByAdmin.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateTemplateCmdByAdmin.java @@ -24,4 +24,5 @@ @APICommand(name = "updateTemplate", description = "Updates attributes of a template.", responseObject = TemplateResponse.class, responseView = ResponseView.Full, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class UpdateTemplateCmdByAdmin extends UpdateTemplateCmd implements AdminCmd {} +public class UpdateTemplateCmdByAdmin extends UpdateTemplateCmd implements AdminCmd { +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateVnfTemplateCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateVnfTemplateCmdByAdmin.java new file mode 100644 index 000000000000..0a40d78f9719 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/UpdateVnfTemplateCmdByAdmin.java @@ -0,0 +1,30 @@ +// 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.template; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd; +import org.apache.cloudstack.api.response.TemplateResponse; + +@APICommand(name = "updateVnfTemplate", + description = "Updates a template to VNF template or attributes of a VNF template.", + responseObject = TemplateResponse.class, responseView = ResponseView.Full, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class UpdateVnfTemplateCmdByAdmin extends UpdateVnfTemplateCmd implements AdminCmd { +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVnfApplianceCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVnfApplianceCmdByAdmin.java new file mode 100644 index 000000000000..d6c51a352150 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVnfApplianceCmdByAdmin.java @@ -0,0 +1,31 @@ +// 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.vm; + +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.response.UserVmResponse; + +@APICommand(name = "deployVnfAppliance", + description = "Creates and automatically starts a VNF appliance based on a service offering, disk offering, and template.", + responseObject = UserVmResponse.class, + responseView = ResponseObject.ResponseView.Full, + entityType = {VirtualMachine.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = true) +public class DeployVnfApplianceCmdByAdmin extends DeployVMCmdByAdmin { +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteVnfTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteVnfTemplateCmd.java new file mode 100755 index 000000000000..efa4c2da108a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteVnfTemplateCmd.java @@ -0,0 +1,46 @@ +// 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.template; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.response.SuccessResponse; + +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.user.Account; + +@APICommand(name = "deleteVnfTemplate", + responseObject = SuccessResponse.class, + description = "Deletes a VNF template from the system. All virtual machines using the deleted template will not be affected.", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class DeleteVnfTemplateCmd extends DeleteTemplateCmd { + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + VirtualMachineTemplate template = _entityMgr.findById(VirtualMachineTemplate.class, getId()); + if (template != null) { + return template.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java index a64ce195c0fb..26d79084531e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java @@ -96,6 +96,15 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User description = "comma separated list of template details requested, value can be a list of [ all, min]") private List viewDetails; + @Parameter(name = ApiConstants.TEMPLATE_TYPE, type = CommandType.STRING, + description = "the type of the template", since = "4.19.0") + private String templateType; + + @Parameter(name = ApiConstants.IS_VNF, type = CommandType.BOOLEAN, + description = "flag to list VNF templates or not; true if need to list VNF templates, false otherwise.", + since = "4.19.0") + private Boolean isVnf; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -151,6 +160,10 @@ public Long getParentTemplateId() { return parentTemplateId; } + public String getTemplateType() { + return templateType; + } + public boolean listInReadyState() { Account account = CallContext.current().getCallingAccount(); @@ -175,6 +188,10 @@ public Boolean getShowIcon () { return showIcon != null ? showIcon : false; } + public Boolean getVnf() { + return isVnf; + } + @Override public String getCommandName() { return s_name; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListVnfTemplatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListVnfTemplatesCmd.java new file mode 100644 index 000000000000..e2c4086d6f5b --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListVnfTemplatesCmd.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 org.apache.cloudstack.api.command.user.template; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.TemplateResponse; + +import com.cloud.template.VirtualMachineTemplate; + +@APICommand(name = "listVnfTemplates", description = "List all public, private, and privileged VNF templates.", + responseObject = TemplateResponse.class, entityType = {VirtualMachineTemplate.class}, responseView = ResponseView.Restricted, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListVnfTemplatesCmd extends ListTemplatesCmd implements UserCmd { +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java index 5709e3ed9007..0a087888d521 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java @@ -143,6 +143,7 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd { description = "true if template contains XS/VMWare tools inorder to support dynamic scaling of VM cpu/memory") protected Boolean isDynamicallyScalable; + @Deprecated @Parameter(name = ApiConstants.ROUTING, type = CommandType.BOOLEAN, description = "true if the template type is routing i.e., if template is used to deploy router") protected Boolean isRoutingType; @@ -168,6 +169,11 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd { description = "(VMware only) true if VM deployments should preserve all the configurations defined for this template", since = "4.15.1") protected Boolean deployAsIs; + @Parameter(name = ApiConstants.TEMPLATE_TYPE, type = CommandType.STRING, + description = "the type of the template. Valid options are: USER/VNF (for all users) and SYSTEM/ROUTING/BUILTIN (for admins only).", + since = "4.19.0") + private String templateType; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -285,6 +291,10 @@ public boolean isDeployAsIs() { Boolean.TRUE.equals(deployAsIs); } + public String getTemplateType() { + return templateType; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -315,7 +325,7 @@ public void execute() throws ResourceAllocationException { VirtualMachineTemplate template = _templateService.registerTemplate(this); if (template != null) { - ListResponse response = new ListResponse(); + ListResponse response = new ListResponse<>(); List templateResponses = _responseGenerator.createTemplateResponses(getResponseView(), template, getZoneIds(), false); response.setResponses(templateResponses); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterVnfTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterVnfTemplateCmd.java new file mode 100644 index 000000000000..b9b8101f4885 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterVnfTemplateCmd.java @@ -0,0 +1,77 @@ +// 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.template; + +import java.util.List; +import java.util.Map; + +import com.cloud.network.VNF; +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.ResponseView; +import org.apache.cloudstack.api.response.TemplateResponse; +import org.apache.cloudstack.storage.template.VnfTemplateManager; + +@APICommand(name = "registerVnfTemplate", + description = "Registers an existing VNF template into the CloudStack cloud. ", + responseObject = TemplateResponse.class, responseView = ResponseView.Restricted, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class RegisterVnfTemplateCmd extends RegisterTemplateCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.VNF_NICS, + type = CommandType.MAP, + description = "VNF nics in key/value pairs using format details[i].keyname=keyvalue. " + + " Example: vnfnics[0].deviceid=0&&vnfnics[0].name=FirstNIC&&vnfnics[0].required=true" + + "&&vnfnics[1].deviceid=1&&vnfnics[1].name=SecondNIC") + protected Map vnfNics; + + @Parameter(name = ApiConstants.VNF_DETAILS, + type = CommandType.MAP, + description = "VNF details in key/value pairs using format details[i].keyname=keyvalue. " + + "Example: vnfdetails[0].vendor=xxx&&details[0].version=2.0") + protected Map vnfDetails; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public List getVnfNics() { + return VnfTemplateManager.getVnfNicsList(this.vnfNics); + } + + public Map getVnfDetails() { + return convertDetailsToMap(vnfDetails); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + protected void validateParameters() { + super.validateParameters(); + + VnfTemplateManager.validateApiCommandParams(this, null); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java index 28ecd453d266..2afa6a98b13d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java @@ -16,10 +16,11 @@ // under the License. package org.apache.cloudstack.api.command.user.template; -import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoCmd; import org.apache.cloudstack.api.Parameter; @@ -41,7 +42,8 @@ public class UpdateTemplateCmd extends BaseUpdateTemplateOrIsoCmd implements Use //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = "templatetype", type = CommandType.STRING, description = "the type of the template") + @Parameter(name = ApiConstants.TEMPLATE_TYPE, type = CommandType.STRING, + description = "the type of the template. Valid options are: USER/VNF (for all users) and SYSTEM/ROUTING/BUILTIN (for admins only).") private String templateType; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateVnfTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateVnfTemplateCmd.java new file mode 100644 index 000000000000..e8a9568c31bd --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateVnfTemplateCmd.java @@ -0,0 +1,77 @@ +// 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.template; + +import com.cloud.network.VNF; + +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.ResponseView; +import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.TemplateResponse; +import org.apache.cloudstack.storage.template.VnfTemplateManager; + +import java.util.List; +import java.util.Map; + +@APICommand(name = "updateVnfTemplate", description = "Updates a template to VNF template or attributes of a VNF template.", + responseObject = TemplateResponse.class, responseView = ResponseView.Restricted, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class UpdateVnfTemplateCmd extends UpdateTemplateCmd implements UserCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.VNF_NICS, + type = CommandType.MAP, + description = "VNF nics in key/value pairs using format details[i].keyname=keyvalue. " + + " Example: vnfnics[0].deviceid=0&&vnfnics[0].name=FirstNIC&&vnfnics[0].required=true" + + "&&vnfnics[1].deviceid=1&&vnfnics[1].name=SecondNIC") + protected Map vnfNics; + + @Parameter(name = ApiConstants.VNF_DETAILS, + type = CommandType.MAP, + description = "VNF details in key/value pairs using format details[i].keyname=keyvalue. " + + "Example: vnfdetails[0].vendor=xxx&&details[0].version=2.0") + protected Map vnfDetails; + + @Parameter(name = ApiConstants.CLEAN_UP_VNF_DETAILS, + type = CommandType.BOOLEAN, + description = "optional boolean field, which indicates if VNF details should be cleaned up or not") + private Boolean cleanupVnfDetails = null; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + + public List getVnfNics() { + return VnfTemplateManager.getVnfNicsList(this.vnfNics); + } + + public Map getVnfDetails() { + return convertDetailsToMap(vnfDetails); + } + + public boolean isCleanupVnfDetails(){ + return cleanupVnfDetails == null ? false : cleanupVnfDetails.booleanValue(); + } +} 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 new file mode 100644 index 000000000000..8d32cbb65430 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVnfApplianceCmd.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 org.apache.cloudstack.api.command.user.vm; + +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.response.UserVmResponse; + +@APICommand(name = "deployVnfAppliance", + description = "Creates and automatically starts a VNF appliance based on a service offering, disk offering, and template.", + responseObject = UserVmResponse.class, + responseView = ResponseObject.ResponseView.Restricted, + entityType = {VirtualMachine.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class DeployVnfApplianceCmd extends DeployVMCmd { +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java index 07d83b47d3cf..9ec625cf3cf9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java @@ -189,6 +189,10 @@ public Long getZoneId() { return zoneId; } + @Parameter(name = ApiConstants.IS_VNF, type = CommandType.BOOLEAN, + description = "flag to list vms created from VNF templates (as known as VNF appliances) or not; true if need to list VNF appliances, false otherwise.", + since = "4.19.0") + private Boolean isVnf; public Long getNetworkId() { return networkId; @@ -266,6 +270,10 @@ public Boolean getAccumulate() { return accumulate; } + public Boolean getVnf() { + return isVnf; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ChildTemplateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ChildTemplateResponse.java index b036cd48e879..8f5b5de29194 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ChildTemplateResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ChildTemplateResponse.java @@ -39,7 +39,7 @@ public class ChildTemplateResponse extends BaseResponse { @Param(description = "the size of the template") private Integer size; - @SerializedName("templatetype") + @SerializedName(ApiConstants.TEMPLATE_TYPE) @Param(description = "the type of the template") private String templateType; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java index bd09d0987087..3abd44941d94 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java @@ -123,7 +123,7 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements @Param(description = "the physical size of the template") private Long physicalSize; - @SerializedName(ApiConstants.TEMPLATETYPE) + @SerializedName(ApiConstants.TEMPLATE_TYPE) @Param(description = "the type of the template") private String templateType; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java index 114403da7bcb..906529c13abd 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java @@ -16,9 +16,12 @@ // under the License. package org.apache.cloudstack.api.response; +import java.util.ArrayList; import java.util.Comparator; import java.util.Date; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; @@ -130,6 +133,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "the name of the template for the virtual machine") private String templateName; + @SerializedName(ApiConstants.TEMPLATE_TYPE) + @Param(description = "the type of the template for the virtual machine", since = "4.19.0") + private String templateType; + @SerializedName("templatedisplaytext") @Param(description = " an alternate display text of the template for the virtual machine") private String templateDisplayText; @@ -360,6 +367,14 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @SerializedName(ApiConstants.USER_DATA_DETAILS) @Param(description="list of variables and values for the variables declared in userdata", since = "4.18.0") private String userDataDetails; + @SerializedName(ApiConstants.VNF_NICS) + @Param(description = "NICs of the VNF appliance", since = "4.19.0") + private List vnfNics; + + @SerializedName(ApiConstants.VNF_DETAILS) + @Param(description = "VNF details", since = "4.19.0") + private Map vnfDetails; + public UserVmResponse() { securityGroupList = new LinkedHashSet<>(); nics = new TreeSet<>(Comparator.comparingInt(x -> Integer.parseInt(x.getDeviceId()))); @@ -1045,4 +1060,49 @@ public void setUserDataDetails(String userDataDetails) { this.userDataDetails = userDataDetails; } + public Long getBytesReceived() { + return bytesReceived; + } + + public Long getBytesSent() { + return bytesSent; + } + + public String getTemplateType() { + return templateType; + } + + public void setTemplateType(String templateType) { + this.templateType = templateType; + } + + public List getVnfNics() { + return vnfNics; + } + + public void setVnfNics(List vnfNics) { + this.vnfNics = vnfNics; + } + + public Map getVnfDetails() { + return vnfDetails; + } + + public void setVnfDetails(Map vnfDetails) { + this.vnfDetails = vnfDetails; + } + + public void addVnfNic(VnfNicResponse vnfNic) { + if (this.vnfNics == null) { + this.vnfNics = new ArrayList<>(); + } + this.vnfNics.add(vnfNic); + } + + public void addVnfDetail(String key, String value) { + if (this.vnfDetails == null) { + this.vnfDetails = new LinkedHashMap<>(); + } + this.vnfDetails.put(key,value); + } } 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 new file mode 100644 index 000000000000..277c09ed9ae9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/VnfNicResponse.java @@ -0,0 +1,82 @@ +// 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 com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; + +@SuppressWarnings("unused") +public class VnfNicResponse { + @SerializedName(ApiConstants.DEVICE_ID) + @Param(description = "Device id of the NIC") + private long deviceId; + + @SerializedName(ApiConstants.NAME) + @Param(description = "Name of the NIC") + private String name; + + @SerializedName(ApiConstants.REQUIRED) + @Param(description = "True if the NIC is required. False if optional") + private Boolean required; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "Description of the NIC") + private String description; + + @SerializedName(ApiConstants.NETWORK_ID) + @Param(description = "Network id of the NIC") + private String networkId; + + @SerializedName(ApiConstants.NETWORK_NAME) + @Param(description = "Network name of the NIC") + private String networkName; + + public void setDeviceId(long deviceId) { + this.deviceId = deviceId; + } + + public void setName(String name) { + this.name = name; + } + + public void setRequired(Boolean required) { + this.required = required; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setNetworkId(String networkId) { + this.networkId = networkId; + } + + public void setNetworkName(String networkName) { + this.networkName = networkName; + } + + public VnfNicResponse() { + } + + public VnfNicResponse(long deviceId, String name, Boolean required, String description) { + this.deviceId = deviceId; + this.name = name; + this.required = required; + this.description = description; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VnfTemplateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VnfTemplateResponse.java new file mode 100644 index 000000000000..4cbf2af990b9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/VnfTemplateResponse.java @@ -0,0 +1,52 @@ +// 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 com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@SuppressWarnings("unused") +public class VnfTemplateResponse extends TemplateResponse { + + @SerializedName(ApiConstants.VNF_NICS) + @Param(description = "NICs of the VNF template") + private List vnfNics; + + @SerializedName(ApiConstants.VNF_DETAILS) + @Param(description = "VNF details") + private Map vnfDetails; + + public void addVnfNic(VnfNicResponse vnfNic) { + if (this.vnfNics == null) { + this.vnfNics = new ArrayList<>(); + } + this.vnfNics.add(vnfNic); + } + + public void addVnfDetail(String key, String value) { + if (this.vnfDetails == null) { + this.vnfDetails = new LinkedHashMap<>(); + } + this.vnfDetails.put(key,value); + } +} 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 new file mode 100644 index 000000000000..0eef6536a7e7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManager.java @@ -0,0 +1,148 @@ +// 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.storage.template; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.VNF; +import com.cloud.storage.Storage; +import com.cloud.template.VirtualMachineTemplate; +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.cloudstack.framework.config.ConfigKey; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public interface VnfTemplateManager { + + ConfigKey VnfTemplateAndApplianceEnabled = new ConfigKey("Advanced", Boolean.class, + "vnf.template.appliance.enabled", + "true", + "Indicates whether the creation of VNF templates and VNF appliances is enabled or not.", + false); + + void persistVnfTemplate(long templateId, RegisterVnfTemplateCmd cmd); + + void updateVnfTemplate(long templateId, UpdateVnfTemplateCmd cmd); + + void validateVnfApplianceNics(VirtualMachineTemplate template, List networkIds); + + static List getVnfNicsList(Map vnfNics) { + List nicsList = new ArrayList<>(); + if (MapUtils.isNotEmpty(vnfNics)) { + Collection nicsCollection = vnfNics.values(); + Iterator iter = nicsCollection.iterator(); + while (iter.hasNext()) { + HashMap nicDetails = (HashMap)iter.next(); + String deviceIdString = nicDetails.get("deviceid"); + String name = nicDetails.get("name"); + String requiredString = nicDetails.get("required"); + String description = nicDetails.get("description"); + Integer deviceId = null; + if (StringUtils.isAnyBlank(name, deviceIdString)) { + throw new InvalidParameterValueException("VNF nic name and deviceid cannot be null"); + } + try { + deviceId = Integer.parseInt(deviceIdString); + } catch (NumberFormatException e) { + 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)); + } + } + Collections.sort(nicsList, Comparator.comparing(VNF.VnfNic::getDeviceId)); + return nicsList; + } + + static void validateApiCommandParams(Map vnfDetails, List vnfNics, String templateType) { + if (templateType != null && !Storage.TemplateType.VNF.name().equals(templateType)) { + throw new InvalidParameterValueException("The template type must be VNF for VNF templates."); + } + + if (vnfDetails != null) { + for (String vnfDetail : vnfDetails.keySet()) { + if (!EnumUtils.isValidEnumIgnoreCase(VNF.VnfDetail.class, vnfDetail) && + !EnumUtils.isValidEnumIgnoreCase(VNF.AccessDetail.class, vnfDetail)) { + throw new InvalidParameterValueException(String.format("Invalid VNF detail found: %s. Valid values are %s and %s", vnfDetail, + Arrays.stream(VNF.AccessDetail.values()).map(method -> method.toString()).collect(Collectors.joining(", ")), + Arrays.stream(VNF.VnfDetail.values()).map(method -> method.toString()).collect(Collectors.joining(", ")))); + } + if (vnfDetails.get(vnfDetail) == null) { + throw new InvalidParameterValueException("Empty value found for VNF detail: " + vnfDetail); + } + if (VNF.AccessDetail.ACCESS_METHODS.name().equalsIgnoreCase(vnfDetail)) { + String[] accessMethods = vnfDetails.get(vnfDetail).split(","); + for (String accessMethod : accessMethods) { + if (VNF.AccessMethod.fromValue(accessMethod.trim()) == null) { + throw new InvalidParameterValueException(String.format("Invalid VNF access method found: %s. Valid values are %s", accessMethod, + Arrays.stream(VNF.AccessMethod.values()).map(method -> method.toString()).sorted().collect(Collectors.joining(", ")))); + } + } + } + } + } + + VnfTemplateManager.validateVnfNics(vnfNics); + } + + static void validateVnfNics(List nicsList) { + int deviceId = 0; + boolean required = true; + for (VNF.VnfNic nic : nicsList) { + if (nic.getDeviceId() != deviceId) { + throw new InvalidParameterValueException(String.format("deviceid must be constant 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)); + } + deviceId ++; + required = nic.isRequired(); + } + } + + static void validateApiCommandParams(BaseCmd cmd, VirtualMachineTemplate template) { + if (cmd instanceof RegisterVnfTemplateCmd) { + RegisterVnfTemplateCmd registerCmd = (RegisterVnfTemplateCmd) cmd; + VnfTemplateManager.validateApiCommandParams(registerCmd.getVnfDetails(), registerCmd.getVnfNics(), registerCmd.getTemplateType()); + } else if (cmd instanceof UpdateVnfTemplateCmd) { + UpdateVnfTemplateCmd updateCmd = (UpdateVnfTemplateCmd) cmd; + if (!Storage.TemplateType.VNF.equals(template.getTemplateType())) { + throw new InvalidParameterValueException(String.format("Cannot update as template %s is not a VNF template. The template type is %s.", updateCmd.getId(), template.getTemplateType())); + } + VnfTemplateManager.validateApiCommandParams(updateCmd.getVnfDetails(), updateCmd.getVnfNics(), updateCmd.getTemplateType()); + } else if (cmd instanceof DeleteVnfTemplateCmd) { + if (!Storage.TemplateType.VNF.equals(template.getTemplateType())) { + DeleteVnfTemplateCmd deleteCmd = (DeleteVnfTemplateCmd) cmd; + throw new InvalidParameterValueException(String.format("Cannot delete as Template %s is not a VNF template. The template type is %s.", deleteCmd.getId(), template.getTemplateType())); + } + } + } +} 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 new file mode 100644 index 000000000000..1e2145970de8 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/storage/template/VnfTemplateManagerTest.java @@ -0,0 +1,201 @@ +// 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.storage.template; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.VNF.VnfNic; +import com.cloud.storage.Storage; +import com.cloud.template.VirtualMachineTemplate; +import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd; +import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class VnfTemplateManagerTest { + + @Test + public void testGetVnfNicsListAllGood() { + final Map vnfNics = new HashMap<>(); + vnfNics.put("0", new HashMap<>(Map.ofEntries( + Map.entry("deviceid", "1"), + Map.entry("name", "eth1"), + Map.entry("required", "true"), + Map.entry("description", "The second NIC of VNF appliance") + ))); + vnfNics.put("1", new HashMap<>(Map.ofEntries( + Map.entry("deviceid", "2"), + Map.entry("name", "eth2"), + Map.entry("required", "false"), + Map.entry("description", "The third NIC of VNF appliance") + ))); + vnfNics.put("2", new HashMap<>(Map.ofEntries( + Map.entry("deviceid", "0"), + Map.entry("name", "eth0"), + Map.entry("description", "The first NIC of VNF appliance") + ))); + List nicsList = VnfTemplateManager.getVnfNicsList(vnfNics); + Assert.assertEquals(3, nicsList.size()); + Assert.assertEquals(0, nicsList.get(0).getDeviceId()); + Assert.assertEquals("eth0", nicsList.get(0).getName()); + Assert.assertTrue(nicsList.get(0).isRequired()); + Assert.assertEquals(1, nicsList.get(1).getDeviceId()); + Assert.assertEquals("eth1", nicsList.get(1).getName()); + Assert.assertTrue(nicsList.get(1).isRequired()); + Assert.assertEquals(2, nicsList.get(2).getDeviceId()); + Assert.assertEquals("eth2", nicsList.get(2).getName()); + Assert.assertFalse(nicsList.get(2).isRequired()); + } + + @Test(expected = InvalidParameterValueException.class) + public void testGetVnfNicsListWithEmptyName() { + final Map vnfNics = new HashMap<>(); + vnfNics.put("0", new HashMap<>(Map.ofEntries( + Map.entry("deviceid", "1"), + Map.entry("required", "true"), + Map.entry("description", "The second NIC of VNF appliance") + ))); + + List nicsList = VnfTemplateManager.getVnfNicsList(vnfNics); + } + + @Test(expected = InvalidParameterValueException.class) + public void testGetVnfNicsListWithEmptyDeviceId() { + final Map vnfNics = new HashMap<>(); + vnfNics.put("0", new HashMap<>(Map.ofEntries( + Map.entry("name", "eth1"), + Map.entry("required", "true"), + Map.entry("description", "The second NIC of VNF appliance") + ))); + + List nicsList = VnfTemplateManager.getVnfNicsList(vnfNics); + } + + @Test(expected = InvalidParameterValueException.class) + public void testGetVnfNicsListWithInvalidDeviceId() { + final Map vnfNics = new HashMap<>(); + vnfNics.put("0", new HashMap<>(Map.ofEntries( + Map.entry("deviceid", "invalid"), + Map.entry("name", "eth1"), + Map.entry("required", "true"), + Map.entry("description", "The second NIC of VNF appliance") + ))); + + List nicsList = VnfTemplateManager.getVnfNicsList(vnfNics); + } + + @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")); + 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")); + 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")); + 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")); + VnfTemplateManager.validateVnfNics(nicsList); + } + + @Test + public void testValidateApiCommandParamsAllGood() { + VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); + Mockito.when(template.getTemplateType()).thenReturn(Storage.TemplateType.VNF); + RegisterVnfTemplateCmd cmd = new RegisterVnfTemplateCmd(); + + VnfTemplateManager.validateApiCommandParams(cmd, template); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidateApiCommandParamsInvalidAccessMethods() { + VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); + Mockito.when(template.getTemplateType()).thenReturn(Storage.TemplateType.VNF); + UpdateVnfTemplateCmd cmd = new UpdateVnfTemplateCmd(); + Map vnfDetails = new HashMap<>(); + vnfDetails.put("0", new HashMap<>(Map.ofEntries( + Map.entry("accessMethods", "invalid") + ))); + ReflectionTestUtils.setField(cmd,"vnfDetails", vnfDetails); + + VnfTemplateManager.validateApiCommandParams(cmd, template); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidateApiCommandParamsInvalidAccessDetails() { + VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); + Mockito.when(template.getTemplateType()).thenReturn(Storage.TemplateType.VNF); + UpdateVnfTemplateCmd cmd = new UpdateVnfTemplateCmd(); + Map accessDetails = new HashMap<>(); + accessDetails.put("0", new HashMap<>(Map.ofEntries( + Map.entry("username", "admin"), + Map.entry("password", "password"), + Map.entry("invalid", "value") + ))); + ReflectionTestUtils.setField(cmd,"vnfDetails", accessDetails); + + VnfTemplateManager.validateApiCommandParams(cmd, template); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidateApiCommandParamsInvalidVnfDetails() { + VirtualMachineTemplate template = Mockito.mock(VirtualMachineTemplate.class); + Mockito.when(template.getTemplateType()).thenReturn(Storage.TemplateType.VNF); + UpdateVnfTemplateCmd cmd = new UpdateVnfTemplateCmd(); + Map vnfDetails = new HashMap<>(); + vnfDetails.put("0", new HashMap<>(Map.ofEntries( + Map.entry("accessMethods", "console"), + Map.entry("username", "admin"), + Map.entry("password", "password"), + Map.entry("version", "4.19.0"), + Map.entry("vendor", "cloudstack"), + Map.entry("invalid", "value") + ))); + ReflectionTestUtils.setField(cmd,"vnfDetails", vnfDetails); + + VnfTemplateManager.validateApiCommandParams(cmd, template); + } +} diff --git a/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java b/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java index a3d9c1b79f1a..7b3df26b40dc 100644 --- a/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java +++ b/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java @@ -18,6 +18,7 @@ import java.util.List; +import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; import org.apache.cloudstack.framework.config.ConfigKey; @@ -29,6 +30,7 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.StorageUnavailableException; import com.cloud.storage.DataStoreRole; +import com.cloud.storage.Storage.TemplateType; import com.cloud.storage.StoragePool; import com.cloud.storage.VMTemplateStoragePoolVO; import com.cloud.storage.VMTemplateVO; @@ -133,5 +135,9 @@ public interface TemplateManager { public static final String MESSAGE_REGISTER_PUBLIC_TEMPLATE_EVENT = "Message.RegisterPublicTemplate.Event"; public static final String MESSAGE_RESET_TEMPLATE_PERMISSION_EVENT = "Message.ResetTemplatePermission.Event"; + TemplateType validateTemplateType(BaseCmd cmd, boolean isAdmin, boolean isCrossZones); + List getTemplateDisksOnImageStore(Long templateId, DataStoreRole role, String configurationId); + + } diff --git a/engine/schema/src/main/java/com/cloud/storage/VnfTemplateDetailVO.java b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateDetailVO.java new file mode 100644 index 000000000000..24d8191fa045 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateDetailVO.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 com.cloud.storage; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.Table; + +import org.apache.cloudstack.api.ResourceDetail; + +@Entity +@Table(name = "vnf_template_details") +public class VnfTemplateDetailVO implements ResourceDetail { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "template_id") + private long resourceId; + + @Column(name = "name") + private String name; + + @Lob + @Column(name = "value", length = 65535) + private String value; + + @Column(name = "display") + private boolean display = true; + + public VnfTemplateDetailVO() { + } + + public VnfTemplateDetailVO(long templateId, String name, String value, boolean display) { + this.resourceId = templateId; + this.name = name; + this.value = value; + this.display = display; + } + + @Override + public long getId() { + return id; + } + + @Override + public long getResourceId() { + return resourceId; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public boolean isDisplay() { + return display; + } + + public void setId(long id) { + this.id = id; + } + + public void setResourceId(long resourceId) { + this.resourceId = resourceId; + } + + public void setName(String name) { + this.name = name; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java new file mode 100644 index 000000000000..4045f3817def --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java @@ -0,0 +1,93 @@ +// 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.storage; + +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "vnf_template_nics") +public class VnfTemplateNicVO implements InternalIdentity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "template_id") + private long templateId; + + @Column(name = "device_id") + private long deviceId; + + @Column(name = "device_name") + private String deviceName; + + @Column(name = "required") + private boolean required = true; + + @Column(name = "description") + private String description; + + public VnfTemplateNicVO() { + } + + public VnfTemplateNicVO(long templateId, long deviceId, String deviceName, boolean required, String description) { + this.templateId = templateId; + this.deviceId = deviceId; + this.deviceName = deviceName; + this.required = required; + this.description = description; + } + + @Override + public String toString() { + return String.format("Template %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "templateId", "deviceId", "required")); + } + + @Override + public long getId() { + return id; + } + + public long getTemplateId() { + return templateId; + } + + public long getDeviceId() { + return deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public boolean getRequired() { + return required; + } + + public String getDescription() { + return description; + } +} diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDao.java new file mode 100644 index 000000000000..c492240bf0f6 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDao.java @@ -0,0 +1,26 @@ +// 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.storage.dao; + +import com.cloud.storage.VnfTemplateDetailVO; +import com.cloud.utils.db.GenericDao; + +import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; + +public interface VnfTemplateDetailsDao extends GenericDao, ResourceDetailsDao { + +} diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDaoImpl.java new file mode 100644 index 000000000000..a4cbfa09fe6f --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateDetailsDaoImpl.java @@ -0,0 +1,31 @@ +// 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.storage.dao; + +import com.cloud.storage.VnfTemplateDetailVO; + +import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; +import org.springframework.stereotype.Component; + +@Component +public class VnfTemplateDetailsDaoImpl extends ResourceDetailsDaoBase implements VnfTemplateDetailsDao { + + @Override + public void addDetail(long resourceId, String key, String value, boolean display) { + super.addDetail(new VnfTemplateDetailVO(resourceId, key, value, display)); + } +} diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDao.java new file mode 100644 index 000000000000..b076f14abc90 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDao.java @@ -0,0 +1,29 @@ +// 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.storage.dao; + +import java.util.List; + +import com.cloud.storage.VnfTemplateNicVO; +import com.cloud.utils.db.GenericDao; + +public interface VnfTemplateNicDao extends GenericDao { + + List listByTemplateId(long templateId); + + void deleteByTemplateId(long templateId); +} diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDaoImpl.java new file mode 100644 index 000000000000..7d4cc34f1a70 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VnfTemplateNicDaoImpl.java @@ -0,0 +1,53 @@ +// 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.storage.dao; + +import java.util.List; + +import com.cloud.storage.VnfTemplateNicVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.TransactionLegacy; + +public class VnfTemplateNicDaoImpl extends GenericDaoBase implements VnfTemplateNicDao { + + protected final SearchBuilder TemplateSearch; + + public VnfTemplateNicDaoImpl() { + TemplateSearch = createSearchBuilder(); + TemplateSearch.and("templateId", TemplateSearch.entity().getTemplateId(), SearchCriteria.Op.EQ); + TemplateSearch.done(); + } + + @Override + public List listByTemplateId(long templateId) { + SearchCriteria sc = TemplateSearch.create(); + sc.setParameters("templateId", templateId); + return listBy(sc); + } + + @Override + public void deleteByTemplateId(long templateId) { + SearchCriteria sc = TemplateSearch.create(); + sc.setParameters("templateId", templateId); + TransactionLegacy txn = TransactionLegacy.currentTxn(); + txn.start(); + remove(sc); + txn.commit(); + } +} 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 04ec733594e2..262bc4cb55f5 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 @@ -277,4 +277,6 @@ + + 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 f7920667210a..a7d3a00c66ca 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 @@ -183,3 +183,226 @@ ALTER TABLE `cloud`.`kubernetes_cluster` MODIFY COLUMN `kubernetes_version_id` b -- Set removed state for all removed accounts UPDATE `cloud`.`account` SET state='removed' WHERE `removed` IS NOT NULL; + + +-- New tables for VNF +CREATE TABLE IF NOT EXISTS `cloud`.`vnf_template_nics` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `template_id` bigint unsigned NOT NULL COMMENT 'id of the VNF template', + `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', + `description` varchar(1024) COMMENT 'Description of the NIC', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_template_id_device_id` (`template_id`, `device_id`), + KEY `fk_vnf_template_nics__template_id` (`template_id`), + CONSTRAINT `fk_vnf_template_nics__template_id` FOREIGN KEY (`template_id`) REFERENCES `vm_template` (`id`) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `cloud`.`vnf_template_details` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `template_id` bigint unsigned NOT NULL COMMENT 'id of the VNF template', + `name` varchar(255) NOT NULL, + `value` varchar(1024) NOT NULL, + `display` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'True if the detail can be displayed to the end user', + PRIMARY KEY (`id`), + KEY `fk_vnf_template_details__template_id` (`template_id`), + CONSTRAINT `fk_vnf_template_details__template_id` FOREIGN KEY (`template_id`) REFERENCES `vm_template` (`id`) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +DROP VIEW IF EXISTS `cloud`.`user_vm_view`; +CREATE + VIEW `user_vm_view` AS +SELECT + `vm_instance`.`id` AS `id`, + `vm_instance`.`name` AS `name`, + `user_vm`.`display_name` AS `display_name`, + `user_vm`.`user_data` AS `user_data`, + `account`.`id` AS `account_id`, + `account`.`uuid` AS `account_uuid`, + `account`.`account_name` AS `account_name`, + `account`.`type` AS `account_type`, + `domain`.`id` AS `domain_id`, + `domain`.`uuid` AS `domain_uuid`, + `domain`.`name` AS `domain_name`, + `domain`.`path` AS `domain_path`, + `projects`.`id` AS `project_id`, + `projects`.`uuid` AS `project_uuid`, + `projects`.`name` AS `project_name`, + `instance_group`.`id` AS `instance_group_id`, + `instance_group`.`uuid` AS `instance_group_uuid`, + `instance_group`.`name` AS `instance_group_name`, + `vm_instance`.`uuid` AS `uuid`, + `vm_instance`.`user_id` AS `user_id`, + `vm_instance`.`last_host_id` AS `last_host_id`, + `vm_instance`.`vm_type` AS `type`, + `vm_instance`.`limit_cpu_use` AS `limit_cpu_use`, + `vm_instance`.`created` AS `created`, + `vm_instance`.`state` AS `state`, + `vm_instance`.`update_time` AS `update_time`, + `vm_instance`.`removed` AS `removed`, + `vm_instance`.`ha_enabled` AS `ha_enabled`, + `vm_instance`.`hypervisor_type` AS `hypervisor_type`, + `vm_instance`.`instance_name` AS `instance_name`, + `vm_instance`.`guest_os_id` AS `guest_os_id`, + `vm_instance`.`display_vm` AS `display_vm`, + `guest_os`.`uuid` AS `guest_os_uuid`, + `vm_instance`.`pod_id` AS `pod_id`, + `host_pod_ref`.`uuid` AS `pod_uuid`, + `vm_instance`.`private_ip_address` AS `private_ip_address`, + `vm_instance`.`private_mac_address` AS `private_mac_address`, + `vm_instance`.`vm_type` AS `vm_type`, + `data_center`.`id` AS `data_center_id`, + `data_center`.`uuid` AS `data_center_uuid`, + `data_center`.`name` AS `data_center_name`, + `data_center`.`is_security_group_enabled` AS `security_group_enabled`, + `data_center`.`networktype` AS `data_center_network_type`, + `host`.`id` AS `host_id`, + `host`.`uuid` AS `host_uuid`, + `host`.`name` AS `host_name`, + `host`.`cluster_id` AS `cluster_id`, + `host`.`status` AS `host_status`, + `host`.`resource_state` AS `host_resource_state`, + `vm_template`.`id` AS `template_id`, + `vm_template`.`uuid` AS `template_uuid`, + `vm_template`.`name` AS `template_name`, + `vm_template`.`type` AS `template_type`, + `vm_template`.`display_text` AS `template_display_text`, + `vm_template`.`enable_password` AS `password_enabled`, + `iso`.`id` AS `iso_id`, + `iso`.`uuid` AS `iso_uuid`, + `iso`.`name` AS `iso_name`, + `iso`.`display_text` AS `iso_display_text`, + `service_offering`.`id` AS `service_offering_id`, + `service_offering`.`uuid` AS `service_offering_uuid`, + `disk_offering`.`uuid` AS `disk_offering_uuid`, + `disk_offering`.`id` AS `disk_offering_id`, + (CASE + WHEN ISNULL(`service_offering`.`cpu`) THEN `custom_cpu`.`value` + ELSE `service_offering`.`cpu` + END) AS `cpu`, + (CASE + WHEN ISNULL(`service_offering`.`speed`) THEN `custom_speed`.`value` + ELSE `service_offering`.`speed` + END) AS `speed`, + (CASE + WHEN ISNULL(`service_offering`.`ram_size`) THEN `custom_ram_size`.`value` + ELSE `service_offering`.`ram_size` + END) AS `ram_size`, + `backup_offering`.`uuid` AS `backup_offering_uuid`, + `backup_offering`.`id` AS `backup_offering_id`, + `service_offering`.`name` AS `service_offering_name`, + `disk_offering`.`name` AS `disk_offering_name`, + `backup_offering`.`name` AS `backup_offering_name`, + `storage_pool`.`id` AS `pool_id`, + `storage_pool`.`uuid` AS `pool_uuid`, + `storage_pool`.`pool_type` AS `pool_type`, + `volumes`.`id` AS `volume_id`, + `volumes`.`uuid` AS `volume_uuid`, + `volumes`.`device_id` AS `volume_device_id`, + `volumes`.`volume_type` AS `volume_type`, + `security_group`.`id` AS `security_group_id`, + `security_group`.`uuid` AS `security_group_uuid`, + `security_group`.`name` AS `security_group_name`, + `security_group`.`description` AS `security_group_description`, + `nics`.`id` AS `nic_id`, + `nics`.`uuid` AS `nic_uuid`, + `nics`.`device_id` AS `nic_device_id`, + `nics`.`network_id` AS `network_id`, + `nics`.`ip4_address` AS `ip_address`, + `nics`.`ip6_address` AS `ip6_address`, + `nics`.`ip6_gateway` AS `ip6_gateway`, + `nics`.`ip6_cidr` AS `ip6_cidr`, + `nics`.`default_nic` AS `is_default_nic`, + `nics`.`gateway` AS `gateway`, + `nics`.`netmask` AS `netmask`, + `nics`.`mac_address` AS `mac_address`, + `nics`.`broadcast_uri` AS `broadcast_uri`, + `nics`.`isolation_uri` AS `isolation_uri`, + `vpc`.`id` AS `vpc_id`, + `vpc`.`uuid` AS `vpc_uuid`, + `networks`.`uuid` AS `network_uuid`, + `networks`.`name` AS `network_name`, + `networks`.`traffic_type` AS `traffic_type`, + `networks`.`guest_type` AS `guest_type`, + `user_ip_address`.`id` AS `public_ip_id`, + `user_ip_address`.`uuid` AS `public_ip_uuid`, + `user_ip_address`.`public_ip_address` AS `public_ip_address`, + `ssh_details`.`value` AS `keypair_names`, + `resource_tags`.`id` AS `tag_id`, + `resource_tags`.`uuid` AS `tag_uuid`, + `resource_tags`.`key` AS `tag_key`, + `resource_tags`.`value` AS `tag_value`, + `resource_tags`.`domain_id` AS `tag_domain_id`, + `domain`.`uuid` AS `tag_domain_uuid`, + `domain`.`name` AS `tag_domain_name`, + `resource_tags`.`account_id` AS `tag_account_id`, + `account`.`account_name` AS `tag_account_name`, + `resource_tags`.`resource_id` AS `tag_resource_id`, + `resource_tags`.`resource_uuid` AS `tag_resource_uuid`, + `resource_tags`.`resource_type` AS `tag_resource_type`, + `resource_tags`.`customer` AS `tag_customer`, + `async_job`.`id` AS `job_id`, + `async_job`.`uuid` AS `job_uuid`, + `async_job`.`job_status` AS `job_status`, + `async_job`.`account_id` AS `job_account_id`, + `affinity_group`.`id` AS `affinity_group_id`, + `affinity_group`.`uuid` AS `affinity_group_uuid`, + `affinity_group`.`name` AS `affinity_group_name`, + `affinity_group`.`description` AS `affinity_group_description`, + `autoscale_vmgroups`.`id` AS `autoscale_vmgroup_id`, + `autoscale_vmgroups`.`uuid` AS `autoscale_vmgroup_uuid`, + `autoscale_vmgroups`.`name` AS `autoscale_vmgroup_name`, + `vm_instance`.`dynamically_scalable` AS `dynamically_scalable`, + `user_data`.`id` AS `user_data_id`, + `user_data`.`uuid` AS `user_data_uuid`, + `user_data`.`name` AS `user_data_name`, + `user_vm`.`user_data_details` AS `user_data_details`, + `vm_template`.`user_data_link_policy` AS `user_data_policy` +FROM + (((((((((((((((((((((((((((((((((((`user_vm` + JOIN `vm_instance` ON (((`vm_instance`.`id` = `user_vm`.`id`) + AND ISNULL(`vm_instance`.`removed`)))) + JOIN `account` ON ((`vm_instance`.`account_id` = `account`.`id`))) + JOIN `domain` ON ((`vm_instance`.`domain_id` = `domain`.`id`))) + LEFT JOIN `guest_os` ON ((`vm_instance`.`guest_os_id` = `guest_os`.`id`))) + LEFT JOIN `host_pod_ref` ON ((`vm_instance`.`pod_id` = `host_pod_ref`.`id`))) + LEFT JOIN `projects` ON ((`projects`.`project_account_id` = `account`.`id`))) + LEFT JOIN `instance_group_vm_map` ON ((`vm_instance`.`id` = `instance_group_vm_map`.`instance_id`))) + LEFT JOIN `instance_group` ON ((`instance_group_vm_map`.`group_id` = `instance_group`.`id`))) + LEFT JOIN `data_center` ON ((`vm_instance`.`data_center_id` = `data_center`.`id`))) + LEFT JOIN `host` ON ((`vm_instance`.`host_id` = `host`.`id`))) + LEFT JOIN `vm_template` ON ((`vm_instance`.`vm_template_id` = `vm_template`.`id`))) + LEFT JOIN `vm_template` `iso` ON ((`iso`.`id` = `user_vm`.`iso_id`))) + LEFT JOIN `volumes` ON ((`vm_instance`.`id` = `volumes`.`instance_id`))) + LEFT JOIN `service_offering` ON ((`vm_instance`.`service_offering_id` = `service_offering`.`id`))) + LEFT JOIN `disk_offering` `svc_disk_offering` ON ((`volumes`.`disk_offering_id` = `svc_disk_offering`.`id`))) + LEFT JOIN `disk_offering` ON ((`volumes`.`disk_offering_id` = `disk_offering`.`id`))) + LEFT JOIN `backup_offering` ON ((`vm_instance`.`backup_offering_id` = `backup_offering`.`id`))) + LEFT JOIN `storage_pool` ON ((`volumes`.`pool_id` = `storage_pool`.`id`))) + LEFT JOIN `security_group_vm_map` ON ((`vm_instance`.`id` = `security_group_vm_map`.`instance_id`))) + LEFT JOIN `security_group` ON ((`security_group_vm_map`.`security_group_id` = `security_group`.`id`))) + LEFT JOIN `user_data` ON ((`user_data`.`id` = `user_vm`.`user_data_id`))) + LEFT JOIN `nics` ON (((`vm_instance`.`id` = `nics`.`instance_id`) + AND ISNULL(`nics`.`removed`)))) + LEFT JOIN `networks` ON ((`nics`.`network_id` = `networks`.`id`))) + LEFT JOIN `vpc` ON (((`networks`.`vpc_id` = `vpc`.`id`) + AND ISNULL(`vpc`.`removed`)))) + LEFT JOIN `user_ip_address` ON ((`user_ip_address`.`vm_id` = `vm_instance`.`id`))) + LEFT JOIN `user_vm_details` `ssh_details` ON (((`ssh_details`.`vm_id` = `vm_instance`.`id`) + AND (`ssh_details`.`name` = 'SSH.KeyPairNames')))) + LEFT JOIN `resource_tags` ON (((`resource_tags`.`resource_id` = `vm_instance`.`id`) + AND (`resource_tags`.`resource_type` = 'UserVm')))) + LEFT JOIN `async_job` ON (((`async_job`.`instance_id` = `vm_instance`.`id`) + AND (`async_job`.`instance_type` = 'VirtualMachine') + AND (`async_job`.`job_status` = 0)))) + LEFT JOIN `affinity_group_vm_map` ON ((`vm_instance`.`id` = `affinity_group_vm_map`.`instance_id`))) + LEFT JOIN `affinity_group` ON ((`affinity_group_vm_map`.`affinity_group_id` = `affinity_group`.`id`))) + LEFT JOIN `autoscale_vmgroup_vm_map` ON ((`autoscale_vmgroup_vm_map`.`instance_id` = `vm_instance`.`id`))) + LEFT JOIN `autoscale_vmgroups` ON ((`autoscale_vmgroup_vm_map`.`vmgroup_id` = `autoscale_vmgroups`.`id`))) + LEFT JOIN `user_vm_details` `custom_cpu` ON (((`custom_cpu`.`vm_id` = `vm_instance`.`id`) + AND (`custom_cpu`.`name` = 'CpuNumber')))) + LEFT JOIN `user_vm_details` `custom_speed` ON (((`custom_speed`.`vm_id` = `vm_instance`.`id`) + AND (`custom_speed`.`name` = 'CpuSpeed')))) + LEFT JOIN `user_vm_details` `custom_ram_size` ON (((`custom_ram_size`.`vm_id` = `vm_instance`.`id`) + AND (`custom_ram_size`.`name` = 'memory')))); \ No newline at end of file diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 50c8f0b435f0..59aa41d42589 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -82,6 +82,7 @@ import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd; import org.apache.cloudstack.api.command.user.tag.ListTagsCmd; import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd; +import org.apache.cloudstack.api.command.user.template.ListVnfTemplatesCmd; import org.apache.cloudstack.api.command.user.vm.ListVMsCmd; import org.apache.cloudstack.api.command.user.vmgroup.ListVMGroupsCmd; import org.apache.cloudstack.api.command.user.volume.ListResourceDetailsCmd; @@ -200,6 +201,7 @@ import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.RouterHealthCheckResult; +import com.cloud.network.VNF; import com.cloud.network.VpcVirtualNetworkApplianceService; import com.cloud.network.dao.RouterHealthCheckResultDao; import com.cloud.network.dao.RouterHealthCheckResultVO; @@ -1158,6 +1160,12 @@ private Pair, Integer> searchForUserVMsInternal(ListVMsCmd cm sb.and("autoScaleVmGroupId", sb.entity().getAutoScaleVmGroupId(), SearchCriteria.Op.EQ); } + Boolean isVnf = cmd.getVnf(); + if (isVnf != null) { + sb.and("templateTypeEQ", sb.entity().getTemplateType(), SearchCriteria.Op.EQ); + sb.and("templateTypeNEQ", sb.entity().getTemplateType(), SearchCriteria.Op.NEQ); + } + // populate the search criteria with the values passed in SearchCriteria sc = sb.create(); @@ -1275,6 +1283,13 @@ private Pair, Integer> searchForUserVMsInternal(ListVMsCmd cm sc.setParameters("keyPairName", keyPairName); } + if (isVnf != null) { + if (isVnf) { + sc.setParameters("templateTypeEQ", TemplateType.VNF); + } else { + sc.setParameters("templateTypeNEQ", TemplateType.VNF); + } + } if (_accountMgr.isRootAdmin(caller.getId())) { if (pod != null) { sc.setParameters("podId", pod); @@ -3643,13 +3658,24 @@ private Pair, Integer> searchForTemplatesInternal(ListTempl boolean showDomr = ((templateFilter != TemplateFilter.selfexecutable) && (templateFilter != TemplateFilter.featured)); HypervisorType hypervisorType = HypervisorType.getType(cmd.getHypervisor()); + String templateType = cmd.getTemplateType(); + if (cmd instanceof ListVnfTemplatesCmd) { + if (templateType == null) { + templateType = TemplateType.VNF.name(); + } else if (!TemplateType.VNF.name().equals(templateType)) { + throw new InvalidParameterValueException("Template type must be VNF when list VNF templates"); + } + } + Boolean isVnf = cmd.getVnf(); + return searchForTemplatesInternal(id, cmd.getTemplateName(), cmd.getKeyword(), templateFilter, false, null, cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), hypervisorType, - showDomr, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedTmpl, cmd.getIds(), parentTemplateId, cmd.getShowUnique()); + showDomr, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedTmpl, cmd.getIds(), parentTemplateId, cmd.getShowUnique(), templateType, isVnf); } private Pair, Integer> searchForTemplatesInternal(Long templateId, String name, String keyword, TemplateFilter templateFilter, boolean isIso, Boolean bootable, Long pageSize, Long startIndex, Long zoneId, HypervisorType hyperType, boolean showDomr, boolean onlyReady, List permittedAccounts, Account caller, - ListProjectResourcesCriteria listProjectResourcesCriteria, Map tags, boolean showRemovedTmpl, List ids, Long parentTemplateId, Boolean showUnique) { + ListProjectResourcesCriteria listProjectResourcesCriteria, Map tags, boolean showRemovedTmpl, List ids, Long parentTemplateId, Boolean showUnique, String templateType, + Boolean isVnf) { // check if zone is configured, if not, just return empty list List hypers = null; @@ -3811,13 +3837,13 @@ else if (!template.isPublicTemplate() && caller.getType() != Account.Type.ADMIN) } return templateChecks(isIso, hypers, tags, name, keyword, hyperType, onlyReady, bootable, zoneId, showDomr, caller, - showRemovedTmpl, parentTemplateId, showUnique, searchFilter, sc); + showRemovedTmpl, parentTemplateId, showUnique, templateType, isVnf, searchFilter, sc); } private Pair, Integer> templateChecks(boolean isIso, List hypers, Map tags, String name, String keyword, HypervisorType hyperType, boolean onlyReady, Boolean bootable, Long zoneId, boolean showDomr, Account caller, - boolean showRemovedTmpl, Long parentTemplateId, Boolean showUnique, + boolean showRemovedTmpl, Long parentTemplateId, Boolean showUnique, String templateType, Boolean isVnf, Filter searchFilter, SearchCriteria sc) { if (!isIso) { // add hypervisor criteria for template case @@ -3899,6 +3925,18 @@ private Pair, Integer> templateChecks(boolean isIso, List, Integer> searchForIsosInternal(ListIsosCmd cm HypervisorType hypervisorType = HypervisorType.getType(cmd.getHypervisor()); return searchForTemplatesInternal(cmd.getId(), cmd.getIsoName(), cmd.getKeyword(), isoFilter, true, cmd.isBootable(), cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), - hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedISO, null, null, cmd.getShowUnique()); + hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedISO, null, null, cmd.getShowUnique(), null, null); } @Override @@ -4027,6 +4065,9 @@ public DetailOptionsResponse listDetailOptions(final ListDetailOptionsCmd cmd) { } fillVMOrTemplateDetailOptions(options, hypervisorType); break; + case VnfTemplate: + fillVnfTemplateDetailOptions(options); + return new DetailOptionsResponse(options); default: throw new CloudRuntimeException("Resource type not supported."); } @@ -4050,6 +4091,19 @@ public ListResponse listResourceIcons(ListResourceIconCmd return responses; } + private void fillVnfTemplateDetailOptions(final Map> options) { + for (VNF.AccessDetail detail : VNF.AccessDetail.values()) { + if (VNF.AccessDetail.ACCESS_METHODS.equals(detail)) { + options.put(detail.name().toLowerCase(), Arrays.stream(VNF.AccessMethod.values()).map(method -> method.toString()).sorted().collect(Collectors.toList())); + } else { + options.put(detail.name().toLowerCase(), Collections.emptyList()); + } + } + for (VNF.VnfDetail detail : VNF.VnfDetail.values()) { + options.put(detail.name().toLowerCase(), Collections.emptyList()); + } + } + private void fillVMOrTemplateDetailOptions(final Map> options, final HypervisorType hypervisorType) { if (options == null) { throw new CloudRuntimeException("Invalid/null detail-options response object passed"); 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 f20a9aa2e133..51d16a275ab4 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 @@ -17,6 +17,7 @@ package com.cloud.api.query.dao; import java.util.ArrayList; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -29,10 +30,16 @@ import com.cloud.deployasis.DeployAsIsConstants; import com.cloud.deployasis.TemplateDeployAsIsDetailVO; import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao; +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.user.dao.UserDataDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.response.VnfNicResponse; +import org.apache.cloudstack.api.response.VnfTemplateResponse; import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; import org.apache.cloudstack.utils.security.DigestHelper; import org.apache.log4j.Logger; @@ -93,6 +100,10 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation tmpltIdPairSearch; @@ -184,7 +195,7 @@ public TemplateResponse newTemplateResponse(EnumSet } } - TemplateResponse templateResponse = new TemplateResponse(); + TemplateResponse templateResponse = initTemplateResponse(template); templateResponse.setDownloadProgress(downloadProgressDetails); templateResponse.setId(template.getUuid()); templateResponse.setName(template.getName()); @@ -299,10 +310,29 @@ public TemplateResponse newTemplateResponse(EnumSet templateResponse.setUserDataParams(template.getUserDataParams()); templateResponse.setUserDataPolicy(template.getUserDataPolicy()); } + templateResponse.setObjectName("template"); return templateResponse; } + private TemplateResponse initTemplateResponse(TemplateJoinVO template) { + TemplateResponse templateResponse = new TemplateResponse(); + if (Storage.TemplateType.VNF.equals(template.getTemplateType())) { + 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())); + } + List details = vnfTemplateDetailsDao.listDetails(template.getId()); + Collections.sort(details, (v1, v2) -> v1.getName().compareToIgnoreCase(v2.getName())); + for (VnfTemplateDetailVO detail : details) { + vnfTemplateResponse.addVnfDetail(detail.getName(), detail.getValue()); + } + templateResponse = vnfTemplateResponse; + } + return templateResponse; + } + private void setDeployAsIsDetails(TemplateJoinVO template, TemplateResponse templateResponse) { if (template.isDeployAsIs()) { List deployAsIsDetails = templateDeployAsIsDetailsDao.listDetails(template.getId()); @@ -320,7 +350,7 @@ private void setDeployAsIsDetails(TemplateJoinVO template, TemplateResponse temp // compared to listTemplates and listIsos. @Override public TemplateResponse newUpdateResponse(TemplateJoinVO result) { - TemplateResponse response = new TemplateResponse(); + TemplateResponse response = initTemplateResponse(result); response.setId(result.getUuid()); response.setName(result.getName()); response.setDisplayText(result.getDisplayText()); 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 b97ae779abc3..9f64dd36f751 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 @@ -18,6 +18,7 @@ import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.List; @@ -38,6 +39,7 @@ import org.apache.cloudstack.api.response.NicSecondaryIpResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VnfNicResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.query.QueryService; @@ -55,6 +57,11 @@ import com.cloud.service.ServiceOfferingDetailsVO; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.GuestOS; +import com.cloud.storage.Storage.TemplateType; +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.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.User; @@ -94,6 +101,10 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation VmDetailSearch; private final SearchBuilder activeVmByIsoSearch; @@ -182,6 +193,7 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us userVmResponse.setTemplateName(userVm.getTemplateName()); userVmResponse.setTemplateDisplayText(userVm.getTemplateDisplayText()); userVmResponse.setPasswordEnabled(userVm.isPasswordEnabled()); + userVmResponse.setTemplateType(userVm.getTemplateType().toString()); } if (details.contains(VMDetails.all) || details.contains(VMDetails.iso)) { userVmResponse.setIsoId(userVm.getIsoUuid()); @@ -418,6 +430,18 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us addVmRxTxDataToResponse(userVm, userVmResponse); + 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())); + } + List vnfDetails = vnfTemplateDetailsDao.listDetails(userVm.getTemplateId()); + Collections.sort(vnfDetails, (v1, v2) -> v1.getName().compareToIgnoreCase(v2.getName())); + for (VnfTemplateDetailVO detail : vnfDetails) { + userVmResponse.addVnfDetail(detail.getName(), detail.getValue()); + } + } + return userVmResponse; } diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java index 4fa4581f00d4..a465e8990e72 100644 --- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java @@ -34,6 +34,7 @@ import com.cloud.network.Network.GuestType; import com.cloud.network.Networks.TrafficType; import com.cloud.resource.ResourceState; +import com.cloud.storage.Storage.TemplateType; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.Volume; import com.cloud.user.Account; @@ -191,6 +192,9 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro @Column(name = "template_name") private String templateName; + @Column(name = "template_type") + private TemplateType templateType; + @Column(name = "template_display_text", length = 4096) private String templateDisplayText; @@ -632,6 +636,10 @@ public String getTemplateName() { return templateName; } + public TemplateType getTemplateType() { + return templateType; + } + public String getTemplateDisplayText() { return templateDisplayText; } diff --git a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java index a9cd9478c58b..74347d1c0570 100644 --- a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java +++ b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java @@ -275,7 +275,8 @@ public TemplateProfile prepare(RegisterTemplateCmd cmd) throws ResourceAllocatio Account owner = _accountMgr.getAccount(cmd.getEntityOwnerId()); _accountMgr.checkAccess(caller, null, true, owner); - boolean isRouting = (cmd.isRoutingType() == null) ? false : cmd.isRoutingType(); + TemplateType templateType = templateMgr.validateTemplateType(cmd, _accountMgr.isAdmin(caller.getAccountId()), + CollectionUtils.isEmpty(cmd.getZoneIds())); List zoneId = cmd.getZoneIds(); // ignore passed zoneId if we are using region wide image store @@ -305,7 +306,7 @@ public TemplateProfile prepare(RegisterTemplateCmd cmd) throws ResourceAllocatio } return prepare(false, CallContext.current().getCallingUserId(), cmd.getTemplateName(), cmd.getDisplayText(), cmd.getBits(), cmd.isPasswordEnabled(), cmd.getRequiresHvm(), cmd.getUrl(), cmd.isPublic(), cmd.isFeatured(), cmd.isExtractable(), cmd.getFormat(), cmd.getOsTypeId(), zoneId, hypervisorType, cmd.getChecksum(), true, - cmd.getTemplateTag(), owner, details, cmd.isSshKeyEnabled(), null, cmd.isDynamicallyScalable(), isRouting ? TemplateType.ROUTING : TemplateType.USER, + cmd.getTemplateTag(), owner, details, cmd.isSshKeyEnabled(), null, cmd.isDynamicallyScalable(), templateType, cmd.isDirectDownload(), cmd.isDeployAsIs()); } diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index bb8affc18700..0bad74f6fcfa 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -38,6 +38,7 @@ import com.cloud.storage.VolumeApiService; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.BaseListTemplateOrIsoPermissionsCmd; import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoCmd; import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoPermissionsCmd; @@ -55,8 +56,10 @@ import org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd; import org.apache.cloudstack.api.command.user.template.ListTemplatePermissionsCmd; import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; +import org.apache.cloudstack.api.command.user.template.RegisterVnfTemplateCmd; import org.apache.cloudstack.api.command.user.template.UpdateTemplateCmd; import org.apache.cloudstack.api.command.user.template.UpdateTemplatePermissionsCmd; +import org.apache.cloudstack.api.command.user.template.UpdateVnfTemplateCmd; import org.apache.cloudstack.api.command.user.userdata.LinkUserDataToTemplateCmd; import org.apache.cloudstack.api.response.GetUploadParamsResponse; import org.apache.cloudstack.context.CallContext; @@ -97,6 +100,7 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity; +import org.apache.cloudstack.storage.template.VnfTemplateManager; import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.cloudstack.utils.imagestore.ImageStoreUtil; import org.apache.commons.collections.CollectionUtils; @@ -303,6 +307,8 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, @Inject protected SnapshotHelper snapshotHelper; + @Inject + VnfTemplateManager vnfTemplateManager; private TemplateAdapter getAdapter(HypervisorType type) { TemplateAdapter adapter = null; @@ -360,6 +366,9 @@ public VirtualMachineTemplate registerTemplate(RegisterTemplateCmd cmd) throws U if (template != null) { CallContext.current().putContextParameter(VirtualMachineTemplate.class, template.getUuid()); + if (cmd instanceof RegisterVnfTemplateCmd) { + vnfTemplateManager.persistVnfTemplate(template.getId(), (RegisterVnfTemplateCmd) cmd); + } return template; } else { throw new CloudRuntimeException("Failed to create a template"); @@ -1329,6 +1338,8 @@ public boolean deleteTemplate(DeleteTemplateCmd cmd) { throw new InvalidParameterValueException("Please specify a valid template."); } + VnfTemplateManager.validateApiCommandParams(cmd, template); + TemplateAdapter adapter = getAdapter(template.getHypervisorType()); TemplateProfile profile = adapter.prepareDelete(cmd); return adapter.delete(profile); @@ -2094,22 +2105,11 @@ private VMTemplateVO updateTemplateOrIso(BaseUpdateTemplateOrIsoCmd cmd) { // update template type TemplateType templateType = null; if (cmd instanceof UpdateTemplateCmd) { - String newType = ((UpdateTemplateCmd)cmd).getTemplateType(); - if (newType != null) { - if (!_accountService.isRootAdmin(account.getId())) { - throw new PermissionDeniedException("Parameter templatetype can only be specified by a Root Admin, permission denied"); - } - try { - templateType = TemplateType.valueOf(newType.toUpperCase()); - } catch (IllegalArgumentException ex) { - throw new InvalidParameterValueException("Please specify a valid templatetype: ROUTING / SYSTEM / USER / BUILTIN / PERHOST"); - } - } - if (templateType != null && cmd.isRoutingType() != null && (TemplateType.ROUTING.equals(templateType) != cmd.isRoutingType())) { - throw new InvalidParameterValueException("Please specify a valid templatetype (consistent with isrouting parameter)."); - } - if (templateType != null && (templateType == TemplateType.SYSTEM || templateType == TemplateType.BUILTIN) && !template.isCrossZones()) { - throw new InvalidParameterValueException("System and Builtin templates must be cross zone"); + boolean isAdmin = _accountMgr.isAdmin(account.getId()); + templateType = validateTemplateType(cmd, isAdmin, template.isCrossZones()); + if (cmd instanceof UpdateVnfTemplateCmd) { + VnfTemplateManager.validateApiCommandParams(cmd, template); + vnfTemplateManager.updateVnfTemplate(template.getId(), (UpdateVnfTemplateCmd) cmd); } } @@ -2229,6 +2229,50 @@ else if (details != null && !details.isEmpty()) { return _tmpltDao.findById(id); } + @Override + public TemplateType validateTemplateType(BaseCmd cmd, boolean isAdmin, boolean isCrossZones) { + if (!(cmd instanceof UpdateTemplateCmd) && !(cmd instanceof RegisterTemplateCmd)) { + return null; + } + TemplateType templateType = null; + String newType = null; + Boolean isRoutingType = null; + if (cmd instanceof UpdateTemplateCmd) { + newType = ((UpdateTemplateCmd)cmd).getTemplateType(); + isRoutingType = ((UpdateTemplateCmd)cmd).isRoutingType(); + } else if (cmd instanceof RegisterTemplateCmd) { + newType = ((RegisterTemplateCmd)cmd).getTemplateType(); + isRoutingType = ((RegisterTemplateCmd)cmd).isRoutingType(); + boolean isRouting = Boolean.TRUE.equals(isRoutingType); + templateType = (cmd instanceof RegisterVnfTemplateCmd) ? TemplateType.VNF : (isRouting ? TemplateType.ROUTING : TemplateType.USER); + } + if (newType != null) { + try { + templateType = TemplateType.valueOf(newType.toUpperCase()); + } catch (IllegalArgumentException ex) { + throw new InvalidParameterValueException(String.format("Please specify a valid templatetype: %s", + org.apache.commons.lang3.StringUtils.join(",", TemplateType.values()))); + } + if (!isAdmin && !Arrays.asList(TemplateType.USER, TemplateType.VNF).contains(templateType)) { + if (cmd instanceof RegisterTemplateCmd) { + throw new InvalidParameterValueException(String.format("Users can not register template with template type = %s.", templateType)); + } else if (cmd instanceof UpdateTemplateCmd) { + throw new InvalidParameterValueException(String.format("Users can not update template to template type = %s.", templateType)); + } + } + } + if (templateType != null && isRoutingType != null && (TemplateType.ROUTING.equals(templateType) != isRoutingType)) { + throw new InvalidParameterValueException("Please specify a valid templatetype (consistent with isrouting parameter)."); + } + if (templateType != null && (templateType == TemplateType.SYSTEM || templateType == TemplateType.BUILTIN) && !isCrossZones) { + throw new InvalidParameterValueException("System and Builtin templates must be cross zone."); + } + if (templateType != null && (cmd instanceof RegisterVnfTemplateCmd || cmd instanceof UpdateVnfTemplateCmd) && !TemplateType.VNF.equals(templateType)) { + throw new InvalidParameterValueException("The template type must be VNF for VNF templates."); + } + return templateType; + } + void validateDetails(VMTemplateVO template, Map details) { if (MapUtils.isEmpty(details)) { return; @@ -2251,7 +2295,8 @@ void validateDetails(VMTemplateVO template, Map details) { String msg = String.format("Invalid %s: %s specified. Valid values are: %s", ApiConstants.BOOT_MODE, bootMode, Arrays.toString(ApiConstants.BootMode.values())); s_logger.error(msg); - throw new InvalidParameterValueException(msg); } + throw new InvalidParameterValueException(msg); + } } void verifyTemplateId(Long id) { diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 23be3facd255..050d4168f2bb 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -123,6 +123,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.cloudstack.storage.template.VnfTemplateManager; import org.apache.cloudstack.userdata.UserDataManager; import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; import org.apache.cloudstack.utils.security.ParserUtils; @@ -619,6 +620,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Inject private UserDataManager userDataManager; + @Inject + VnfTemplateManager vnfTemplateManager; + private static final ConfigKey VmIpFetchWaitInterval = new ConfigKey("Advanced", Integer.class, "externaldhcp.vmip.retrieval.interval", "180", "Wait Interval (in seconds) for shared network vm dhcp ip addr fetch for next iteration ", true); @@ -5857,6 +5861,9 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE if (template == null) { throw new InvalidParameterValueException("Unable to use template " + templateId); } + if (TemplateType.VNF.equals(template.getTemplateType())) { + vnfTemplateManager.validateVnfApplianceNics(template, cmd.getNetworkIds()); + } ServiceOfferingJoinVO svcOffering = serviceOfferingJoinDao.findById(serviceOfferingId); 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 new file mode 100644 index 000000000000..58d13b5d585c --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/storage/template/VnfTemplateManagerImpl.java @@ -0,0 +1,152 @@ +// 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.storage.template; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.VNF; +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.utils.component.ManagerBase; +import com.cloud.utils.component.PluggableService; +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; +import org.apache.cloudstack.api.command.admin.vm.DeployVnfApplianceCmdByAdmin; +import org.apache.cloudstack.api.command.user.template.DeleteVnfTemplateCmd; +import org.apache.cloudstack.api.command.user.template.ListVnfTemplatesCmd; +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 org.apache.cloudstack.framework.config.Configurable; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.log4j.Logger; + + +public class VnfTemplateManagerImpl extends ManagerBase implements VnfTemplateManager, PluggableService, Configurable { + + static final Logger LOGGER = Logger.getLogger(VnfTemplateManagerImpl.class); + + @Inject + VnfTemplateDetailsDao vnfTemplateDetailsDao; + @Inject + VnfTemplateNicDao vnfTemplateNicDao; + + @Override + public List> getCommands() { + final List> cmdList = new ArrayList<>(); + if (!VnfTemplateAndApplianceEnabled.value()) { + return cmdList; + } + cmdList.add(RegisterVnfTemplateCmd.class); + cmdList.add(RegisterVnfTemplateCmdByAdmin.class); + cmdList.add(ListVnfTemplatesCmd.class); + cmdList.add(ListVnfTemplatesCmdByAdmin.class); + cmdList.add(UpdateVnfTemplateCmd.class); + cmdList.add(UpdateVnfTemplateCmdByAdmin.class); + cmdList.add(DeleteVnfTemplateCmd.class); + cmdList.add(DeployVnfApplianceCmd.class); + cmdList.add(DeployVnfApplianceCmdByAdmin.class); + return cmdList; + } + + @Override + public void persistVnfTemplate(long templateId, RegisterVnfTemplateCmd cmd) { + persistVnfTemplateNics(templateId, cmd.getVnfNics()); + persistVnfTemplateDetails(templateId, 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()); + vnfTemplateNicDao.persist(vnfTemplateNicVO); + } + } + + private void persistVnfTemplateDetails(long templateId, RegisterVnfTemplateCmd cmd) { + persistVnfTemplateDetails(templateId, cmd.getVnfDetails()); + } + + private void persistVnfTemplateDetails(long templateId, Map vnfDetails) { + 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(",")); + } + vnfTemplateDetailsDao.addDetail(templateId, entry.getKey().toLowerCase(), value, true); + } + } + + @Override + public void updateVnfTemplate(long templateId, UpdateVnfTemplateCmd cmd) { + updateVnfTemplateDetails(templateId, cmd); + updateVnfTemplateNics(templateId, cmd); + } + + private void updateVnfTemplateDetails(long templateId, UpdateVnfTemplateCmd cmd) { + boolean cleanupVnfDetails = cmd.isCleanupVnfDetails(); + if (cleanupVnfDetails) { + vnfTemplateDetailsDao.removeDetails(templateId); + } else if (MapUtils.isNotEmpty(cmd.getVnfDetails())) { + vnfTemplateDetailsDao.removeDetails(templateId); + persistVnfTemplateDetails(templateId, cmd.getVnfDetails()); + } + } + + private void updateVnfTemplateNics(long templateId, UpdateVnfTemplateCmd cmd) { + List nics = cmd.getVnfNics(); + if (CollectionUtils.isEmpty(nics)) { + return; + } + vnfTemplateNicDao.deleteByTemplateId(templateId); + persistVnfTemplateNics(templateId, nics); + } + + @Override + public String getConfigComponentName() { + return VnfTemplateManager.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] { VnfTemplateAndApplianceEnabled }; + } + + @Override + public void validateVnfApplianceNics(VirtualMachineTemplate template, List networkIds) { + if (CollectionUtils.isEmpty(networkIds)) { + throw new InvalidParameterValueException("VNF nics list is empty"); + } + List vnfNics = vnfTemplateNicDao.listByTemplateId(template.getId()); + for (VnfTemplateNicVO vnfNic : vnfNics) { + if (vnfNic.getRequired() && networkIds.size() <= vnfNic.getDeviceId()) { + throw new InvalidParameterValueException("VNF nic is required but not found: " + vnfNic); + } + } + } +} \ No newline at end of file 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 ee676aabbfa8..b7515a541062 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 @@ -352,4 +352,5 @@ + diff --git a/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java b/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java index 8331cff02771..66a7fea0b418 100755 --- a/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java +++ b/server/src/test/java/com/cloud/template/TemplateManagerImplTest.java @@ -91,6 +91,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.cloudstack.storage.template.VnfTemplateManager; import org.apache.cloudstack.test.utils.SpringUtils; import org.junit.After; import org.junit.Assert; @@ -191,6 +192,8 @@ public class TemplateManagerImplTest { @Inject AccountManager _accountMgr; + @Inject + VnfTemplateManager vnfTemplateManager; public class CustomThreadPoolExecutor extends ThreadPoolExecutor { AtomicInteger ai = new AtomicInteger(0); @@ -787,6 +790,11 @@ public HypervisorGuruManager hypervisorGuruManager() { return Mockito.mock(HypervisorGuruManager.class); } + @Bean + public VnfTemplateManager vnfTemplateManager() { + return Mockito.mock(VnfTemplateManager.class); + } + @Bean public SnapshotHelper snapshotHelper() { return Mockito.mock(SnapshotHelper.class); diff --git a/test/integration/smoke/test_vnf_templates.py b/test/integration/smoke/test_vnf_templates.py new file mode 100644 index 000000000000..f69e2280015e --- /dev/null +++ b/test/integration/smoke/test_vnf_templates.py @@ -0,0 +1,338 @@ +# 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. + +""" Smoke tests for VNF templates/appliances +""" +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.base import (Account, + Domain, + Configurations, + ServiceOffering, + VirtualMachine, + Network, + NetworkOffering, + VnfTemplate, + Zone) +from marvin.lib.common import get_zone, get_template +from nose.plugins.attrib import attr + +import time + +VNF_NICS = [{"deviceid": "0", "name": "WAN", "required": "true", "description": "Public WAN"}, + {"deviceid": "1", "name": "LAN-1", "required": "true", "description": "Private LAN-1"}] +NEW_VNF_NICS = [{"deviceid": "0", "name": "WAN", "required": "true", "description": "Public WAN"}, + {"deviceid": "1", "name": "LAN-1", "required": "true", "description": "Private LAN-1"}, + {"deviceid": "2", "name": "LAN-2", "required": "false", "description": "Private LAN-2"}] +VNF_DETAILS = [{"access_methods": "console,https,http", "username": "root"}] +NEW_VNF_DETAILS = [{"access_methods": "console,https,http", "username": "root", "password": "cloudstack"}] + +class TestVnfTemplates(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + testClient = super(TestVnfTemplates, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls._cleanup = [] + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.hypervisor = cls.testClient.getHypervisorInfo() + zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.zone = Zone(zone.__dict__) + cls.template = get_template(cls.apiclient, cls.zone.id) + + cls.domain = Domain.create( + cls.apiclient, + cls.services["domain"] + ) + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls.user = cls.account.user[0] + cls.user_apiclient = cls.testClient.getUserApiClient( + cls.user.username, cls.domain.name + ) + + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["big"] + ) + + cls._cleanup = [ + cls.service_offering, + cls.domain, + cls.account + ] + + cls.vnf_template_config = { + "name": "pfsense", + "displaytext": "pfsense", + "format": "QCOW2", + "url": cls.template.url, + "requireshvm": "True", + "ispublic": "True", + "isextractable": "True", + "hypervisor": cls.hypervisor, + "zoneid": cls.zone.id, + "ostype": "FreeBSD 12 (64-bit)", + "directdownload": False + } + + cls.initial_setting = Configurations.list( + cls.apiclient, + name="vnf.template.appliance.enabled")[0].value + + Configurations.update(cls.apiclient, "vnf.template.appliance.enabled", "true") + + cls.vnf_templates = [] + + @classmethod + def tearDownClass(cls): + Configurations.update(cls.apiclient, "vnf.template.appliance.enabled", cls.initial_setting) + super(TestVnfTemplates, cls).tearDownClass() + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + + def tearDown(self): + super(TestVnfTemplates, self).tearDown() + + @attr(tags=["advanced"], required_hardware="false") + def test_01_register_vnf_template(self): + """Test register VNF template + """ + self.vnf_template = VnfTemplate.register(self.user_apiclient, + self.vnf_template_config, + zoneid=self.zone.id, + hypervisor=self.hypervisor, + vnfnics=VNF_NICS, + vnfdetails=VNF_DETAILS) + self.vnf_templates.append(self.vnf_template) + + @attr(tags=["advanced"], required_hardware="false") + def test_02_list_vnf_template(self): + """Test list VNF template + """ + self.assertIsNotNone(self.vnf_templates, "VNF templates should not be null") + self.assertEqual(1, len(self.vnf_templates), "There should be 1 VNF template") + self.vnf_template = self.vnf_templates[0] + + templates_response = VnfTemplate.list( + self.user_apiclient, + id=self.vnf_template.id, + zoneid=self.zone.id, + templatefilter='self' + ) + + if isinstance(templates_response, list) and len(templates_response) > 0: + template = templates_response[0] + self.assertEqual("VNF", template.templatetype, + "The template type of VNF template should be VNF but actually it is %s" % template.templatetype) + self.assertTrue(isinstance(template.vnfnics, list), "The template vnfnics must be a list") + self.assertEqual(2, len(template.vnfnics), "The VNF template should have 2 VNF nics") + self.assertEqual(2, len(template.vnfdetails.__dict__), "The VNF template should have 2 VNF details") + else: + self.fail("Failed to get VNF templates by listVnfTemplates API") + + """Check if template download will finish in 5 minutes""" + retries = 30 + interval = 10 + while retries > -1: + time.sleep(interval) + templates_response = VnfTemplate.list( + self.user_apiclient, + id=self.vnf_template.id, + zoneid=self.zone.id, + templatefilter='self' + ) + template = templates_response[0] + + if not hasattr(template, 'status') or not template or not template.status: + retries = retries - 1 + continue + + if 'Failed' in template.status: + raise Exception( + "Failed to download template: status - %s" % + template.status) + elif template.status == 'Download Complete' and template.isready: + return + elif 'Downloaded' in template.status: + retries = retries - 1 + continue + elif 'Installing' not in template.status: + if retries >= 0: + retries = retries - 1 + continue + raise Exception( + "Error in downloading template: status - %s" % + template.status) + else: + retries = retries - 1 + raise Exception("Template download failed exception.") + + @attr(tags=["advanced"], required_hardware="false") + def test_03_edit_vnf_template(self): + """Test edit VNF template + """ + + self.assertIsNotNone(self.vnf_templates, "VNF templates should not be null") + self.assertEqual(1, len(self.vnf_templates), "There should be 1 VNF template") + self.vnf_template = self.vnf_templates[0] + + self.vnf_template.update( + self.user_apiclient, + id=self.vnf_template.id, + vnfnics=NEW_VNF_NICS, + vnfdetails=NEW_VNF_DETAILS + ) + + templates_response = VnfTemplate.list( + self.user_apiclient, + id=self.vnf_template.id, + zoneid=self.zone.id, + templatefilter='self' + ) + + if isinstance(templates_response, list) and len(templates_response) > 0: + template = templates_response[0] + self.assertEqual("VNF", template.templatetype, + "The template type of VNF template should be VNF but actually it is %s" % template.templatetype) + self.assertEqual(3, len(template.vnfnics), "The VNF template should have 2 VNF nics") + self.assertEqual(3, len(template.vnfdetails.__dict__), "The VNF template should have 3 VNF details") + else: + self.fail("Failed to get VNF templates by listVnfTemplates API") + + @attr(tags=["advanced"], required_hardware="false") + def test_04_deploy_vnf_appliance(self): + """Test deploy VNF appliance + """ + + self.assertIsNotNone(self.vnf_templates, "VNF templates should not be null") + self.assertEqual(1, len(self.vnf_templates), "There should be 1 VNF template") + self.vnf_template = self.vnf_templates[0] + + templates_response = VnfTemplate.list( + self.user_apiclient, + id=self.vnf_template.id, + zoneid=self.zone.id, + templatefilter='self' + ) + + if isinstance(templates_response, list) and len(templates_response) > 0: + template = templates_response[0] + if not template.isready: + self.fail("VNF template is not Ready") + else: + self.fail("Failed to find VNF template") + + # Create network offerings + self.isolated_network_offering = NetworkOffering.create( + self.apiclient, + self.services["isolated_network_offering"]) + self.cleanup.append(self.isolated_network_offering) + self.isolated_network_offering.update( + self.apiclient, + state='Enabled') + + self.l2_network_offering = NetworkOffering.create( + self.apiclient, + self.services["l2-network_offering"]) + self.cleanup.append(self.l2_network_offering) + self.l2_network_offering.update( + self.apiclient, + state='Enabled') + + # Create networks + isolated_network = Network.create( + self.user_apiclient, + self.services["network"], + networkofferingid=self.isolated_network_offering.id, + zoneid=self.zone.id + ) + self.cleanup.append(isolated_network) + + l2_network_1 = Network.create( + self.user_apiclient, + self.services["l2-network"], + networkofferingid=self.l2_network_offering.id, + zoneid=self.zone.id + ) + self.cleanup.append(l2_network_1) + + l2_network_2 = Network.create( + self.user_apiclient, + self.services["l2-network"], + networkofferingid=self.l2_network_offering.id, + zoneid=self.zone.id + ) + self.cleanup.append(l2_network_2) + + # failed deployment + try: + self.virtual_machine = VirtualMachine.create( + self.user_apiclient, + self.services["virtual_machine"], + zoneid=self.zone.id, + templateid=self.vnf_template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + networkids=[isolated_network.id] + ) + self.cleanup.append(self.virtual_machine) + self.fail("The deployment should fail") + except Exception as e: + pass + + # success deployment + self.virtual_machine = VirtualMachine.create( + self.user_apiclient, + self.services["virtual_machine"], + zoneid=self.zone.id, + templateid=self.vnf_template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + networkids=[isolated_network.id, l2_network_1.id, l2_network_2.id] + ) + self.cleanup.append(self.virtual_machine) + + @attr(tags=["advanced"], required_hardware="false") + def test_05_delete_vnf_template(self): + """Test delete VNF template + """ + + self.assertIsNotNone(self.vnf_templates, "VNF templates should not be null") + self.assertEqual(1, len(self.vnf_templates), "There should be 1 VNF template") + self.vnf_template = self.vnf_templates[0] + + self.vnf_template.delete(self.user_apiclient) + + templates_response = VnfTemplate.list( + self.user_apiclient, + id=self.vnf_template.id, + zoneid=self.zone.id, + templatefilter='self' + ) + self.assertIsNone(templates_response, "The VNF template should be removed") diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index bd102e9c7cc1..75ff7e1d29a0 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -50,6 +50,7 @@ 'SystemVm': 'System VM', 'VirtualMachine': 'Virtual Machine', 'VM': 'Virtual Machine', + 'Vnf': 'Virtual Network Functions', 'Domain': 'Domain', 'Template': 'Template', 'Iso': 'ISO', diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 5906d2f1e3d8..739775da4e5f 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -6700,3 +6700,121 @@ def delete(self, apiclient): cmd.id = self.id cmd.virtualmachineid = self.virtualmachineid return (apiclient.deleteVMSchedule(cmd)) + +class VnfTemplate: + """Manage VNF template life cycle""" + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def register(cls, apiclient, services, zoneid=None, + account=None, domainid=None, hypervisor=None, + projectid=None, details=None, randomize_name=True, + vnfnics=None, vnfdetails=None): + """Create VNF template from URL""" + + # Create template from Virtual machine and Volume ID + cmd = registerVnfTemplate.registerVnfTemplateCmd() + cmd.displaytext = services["displaytext"] + if randomize_name: + cmd.name = "-".join([services["name"], random_gen()]) + else: + cmd.name = services["name"] + cmd.format = services["format"] + if hypervisor: + cmd.hypervisor = hypervisor + elif "hypervisor" in services: + cmd.hypervisor = services["hypervisor"] + + if "ostypeid" in services: + cmd.ostypeid = services["ostypeid"] + elif "ostype" in services: + # Find OSTypeId from Os type + sub_cmd = listOsTypes.listOsTypesCmd() + sub_cmd.description = services["ostype"] + ostypes = apiclient.listOsTypes(sub_cmd) + + if not isinstance(ostypes, list): + raise Exception( + "Unable to find Ostype id with desc: %s" % + services["ostype"]) + cmd.ostypeid = ostypes[0].id + else: + raise Exception( + "Unable to find Ostype is required for registering template") + + cmd.url = services["url"] + + if zoneid: + cmd.zoneid = zoneid + else: + cmd.zoneid = services["zoneid"] + + cmd.isfeatured = services[ + "isfeatured"] if "isfeatured" in services else False + cmd.ispublic = services[ + "ispublic"] if "ispublic" in services else False + cmd.isextractable = services[ + "isextractable"] if "isextractable" in services else False + cmd.isdynamicallyscalable = services["isdynamicallyscalable"] if "isdynamicallyscalable" in services else False + cmd.passwordenabled = services[ + "passwordenabled"] if "passwordenabled" in services else False + cmd.deployasis = services["deployasis"] if "deployasis" in services else False + + if account: + cmd.account = account + + if domainid: + cmd.domainid = domainid + + if projectid: + cmd.projectid = projectid + elif "projectid" in services: + cmd.projectid = services["projectid"] + + if details: + cmd.details = details + + if "directdownload" in services: + cmd.directdownload = services["directdownload"] + + if vnfnics: + cmd.vnfnics = vnfnics + + if vnfdetails: + cmd.vnfdetails = vnfdetails + + # Register Template + template = apiclient.registerVnfTemplate(cmd) + + if isinstance(template, list): + return VnfTemplate(template[0].__dict__) + + def delete(self, apiclient, zoneid=None): + """Delete VNF Template""" + + cmd = deleteVnfTemplate.deleteVnfTemplateCmd() + cmd.id = self.id + if zoneid: + cmd.zoneid = zoneid + apiclient.deleteVnfTemplate(cmd) + + def update(self, apiclient, **kwargs): + """Updates the template details""" + + cmd = updateVnfTemplate.updateVnfTemplateCmd() + cmd.id = self.id + [setattr(cmd, k, v) for k, v in list(kwargs.items())] + return (apiclient.updateVnfTemplate(cmd)) + + @classmethod + def list(cls, apiclient, **kwargs): + """List all templates matching criteria""" + + cmd = listVnfTemplates.listVnfTemplatesCmd() + [setattr(cmd, k, v) for k, v in list(kwargs.items())] + 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 216c6168c5f4..b25d681d6abd 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -2175,6 +2175,24 @@ "label.vmwaredcname": "VMware datacenter name", "label.vmwaredcvcenter": "VMware datacenter vCenter", "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.detail.add": "Add VNF detail", +"label.vnf.detail.remove": "Remove VNF detail", +"label.vnf.details": "VNF details", +"label.vnf.nic.add": "Add VNF nic", +"label.vnf.nic.delete": "Delete VNF nic", +"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.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.nics": "VNF nics", +"label.vnf.settings": "VNF settings", +"label.vnf.templates": "VNF templates", +"label.vnf.template.register": "Register VNF template", "label.vnmc": "VNMC", "label.volgroup": "Volume group", "label.volume": "Volume", @@ -3116,6 +3134,14 @@ "message.vm.state.stopped": "VM is stopped.", "message.vm.state.stopping": "VM is being stopped.", "message.vm.state.unknown": "VM state is unknown.", +"message.vnf.appliance.networks": "Please select networks for the new VNF appliance.", +"message.vnf.error.deviceid.should.be.continuous": "The deviceid of selected VNF nics should be continuous.", +"message.vnf.error.network.is.already.used": "Network has been used by multiple nics of the new VNF appliance.", +"message.vnf.error.no.networks": "Please select networks for nics of the new VNF appliance.", +"message.vnf.error.no.network.for.required.deviceid": "Please select a network for required nic of the new VNF appliance.", +"message.vnf.nic.move.up.fail": "Failed to move up this NIC", +"message.vnf.nic.move.down.fail": "Failed to move down this NIC", +"message.vnf.select.networks": "Please select a network for each VNF nic. ", "message.volume.state.allocated": "The volume is allocated but has not been created yet.", "message.volume.state.attaching": "The volume is attaching to a volume from Ready state.", "message.volume.state.copying": "The volume is being copied from the image store to primary storage, in case it's an uploaded volume.", diff --git a/ui/src/components/view/DetailsTab.vue b/ui/src/components/view/DetailsTab.vue index 301686c34330..205db670c068 100644 --- a/ui/src/components/view/DetailsTab.vue +++ b/ui/src/components/view/DetailsTab.vue @@ -28,6 +28,11 @@

+ + + @@ -159,11 +164,81 @@ export default { customDisplayItems () { return ['ip6routes', 'privatemtu', 'publicmtu'] }, + vnfAccessMethods () { + if (this.resource.templatetype === 'VNF' && ['vm'].includes(this.$route.meta.name)) { + const accessMethodsDescription = [] + const accessMethods = this.resource.vnfdetails?.access_methods || null + const username = this.resource.vnfdetails?.username || null + const password = this.resource.vnfdetails?.password || null + const sshPort = this.resource.vnfdetails?.ssh_port || 22 + const sshUsername = this.resource.vnfdetails?.ssh_user || null + const sshPassword = this.resource.vnfdetails?.ssh_password || null + const httpPath = this.resource.vnfdetails?.http_path || null + const httpPort = this.resource.vnfdetails?.http_port || null + const httpsPath = this.resource.vnfdetails?.https_path || null + const httpsPort = this.resource.vnfdetails?.https_port || null + const webUsername = this.resource.vnfdetails?.web_user || null + const webPassword = this.resource.vnfdetails?.web_password || null + + const credentials = [] + if (username) { + credentials.push(this.$t('label.username') + ' : ' + username) + } + if (password) { + credentials.push(this.$t('label.password') + ' : ' + password) + } + 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) + } + if (webPassword) { + credentials.push('Web ' + this.$t('label.password') + ' : ' + webPassword) + } + + if (accessMethods) { + const accessMethodsArray = accessMethods.split(',') + for (const accessMethod of accessMethodsArray) { + if (accessMethod === 'console') { + accessMethodsDescription.push('- VM Console.') + } else if (accessMethod === 'ssh-password') { + accessMethodsDescription.push('- SSH with password' + (sshPort ? ' (SSH port is ' + sshPort + ').' : '.')) + } 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 : '')) + } + } 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 : '')) + } + } + } + } else { + accessMethodsDescription.push('- VM Console.') + } + if (credentials) { + accessMethodsDescription.push('') + accessMethodsDescription.push('- Credentials:') + for (const credential of credentials) { + accessMethodsDescription.push('* ' + credential) + } + } + return accessMethodsDescription.join('
') + } + return null + }, ipV6Address () { if (this.dataResource.nic && this.dataResource.nic.length > 0) { return this.dataResource.nic.filter(e => { return e.ip6address }).map(e => { return e.ip6address }).join(', ') } - return null }, ip6routes () { diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 8352a179afe2..75b80657aec3 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -39,7 +39,7 @@