From d8c7e34b383d6e6ab4d00f3a07e3b2ab467eca36 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Tue, 31 Jan 2023 15:53:43 +0530 Subject: [PATCH] Improve global settings UI to be more intuitive/logical (#5797) Co-authored-by: Suresh Kumar Anaparti Co-authored-by: nvazquez Co-authored-by: davidjumani Co-authored-by: dahn Co-authored-by: dahn --- .../com/cloud/server/ManagementService.java | 9 + .../apache/cloudstack/api/ApiConstants.java | 5 + .../cloudstack/api/ResponseGenerator.java | 4 + .../admin/config/ListCfgGroupsByCmd.java | 79 ++++ .../command/admin/config/ListCfgsByCmd.java | 127 ++++-- .../response/ConfigurationGroupResponse.java | 76 ++++ .../api/response/ConfigurationResponse.java | 96 +++++ .../ConfigurationSubGroupResponse.java | 50 +++ .../cloudstack/backup/BackupManager.java | 4 +- .../org/apache/cloudstack/ca/CAManager.java | 6 +- .../config/ApiServiceConfiguration.java | 6 +- .../apache/cloudstack/query/QueryService.java | 12 +- .../com/cloud/vm/VirtualMachineManager.java | 2 +- .../configuration/ConfigurationManager.java | 9 + .../com/cloud/network/IpAddressManager.java | 5 +- .../com/cloud/resource/ResourceManager.java | 6 +- .../com/cloud/storage/StorageManager.java | 37 +- .../manager/ClusteredAgentManagerImpl.java | 2 +- .../ConfigurationGroupsAggregator.java | 106 +++++ .../upgrade/dao/Upgrade41610to41700.java | 10 +- .../upgrade/dao/Upgrade41720to41800.java | 7 + ...spring-engine-schema-core-daos-context.xml | 2 + .../META-INF/db/schema-41610to41700.sql | 6 +- .../META-INF/db/schema-41720to41800.sql | 249 +++++++++++- .../ImageStoreProviderManagerImpl.java | 4 +- .../storage/test/ChildTestConfiguration.java | 14 +- .../src/test/resources/component.xml | 6 +- .../cloudstack/config/Configuration.java | 29 +- .../cloudstack/config/ConfigurationGroup.java | 28 ++ .../config/ConfigurationSubGroup.java | 30 ++ .../framework/config/ConfigDepotAdmin.java | 4 + .../framework/config/ConfigKey.java | 70 +++- .../config/dao/ConfigurationGroupDao.java | 25 ++ .../config/dao/ConfigurationGroupDaoImpl.java | 44 +++ .../config/dao/ConfigurationSubGroupDao.java | 31 ++ .../dao/ConfigurationSubGroupDaoImpl.java | 131 +++++++ .../config/impl/ConfigDepotImpl.java | 114 ++++++ .../config/impl/ConfigurationGroupVO.java | 91 +++++ .../config/impl/ConfigurationSubGroupVO.java | 103 +++++ .../config/impl/ConfigurationVO.java | 112 +++++- ...spring-framework-config-system-context.xml | 6 + .../framework/config/ConfigKeyTest.java | 4 +- .../config/impl/ConfigDepotAdminTest.java | 61 ++- .../AsyncJobManagerTestConfiguration.java | 19 +- .../lifecycle/registry/ExtensionRegistry.java | 4 +- .../cluster/KubernetesClusterService.java | 20 +- .../metrics/PrometheusExporterServer.java | 4 +- .../IntegrationTestConfiguration.java | 10 +- .../cloudstack/saml/SAML2AuthManager.java | 4 +- server/conf/migration-components.xml | 2 + .../java/com/cloud/api/ApiResponseHelper.java | 43 +++ .../java/com/cloud/configuration/Config.java | 79 +++- .../ConfigurationManagerImpl.java | 97 ++++- .../VirtualNetworkApplianceManager.java | 20 +- .../VirtualNetworkApplianceManagerImpl.java | 4 +- .../cloud/server/ConfigurationServerImpl.java | 11 + .../cloud/server/ManagementServerImpl.java | 51 +++ .../java/com/cloud/vm/UserVmManagerImpl.java | 12 +- .../agent/lb/IndirectAgentLBServiceImpl.java | 6 +- .../diagnostics/DiagnosticsServiceImpl.java | 4 +- .../fileprocessor/DiagnosticsFilesList.java | 12 +- server/src/test/async-job-component.xml | 2 + .../com/cloud/configuration/ConfigTest.java | 2 +- ...SecurityGroupManagerTestConfiguration.java | 6 +- .../com/cloud/vm/FirstFitPlannerTest.java | 16 + .../vpc/MockConfigurationManagerImpl.java | 18 + .../com/cloud/vpc/VpcTestConfiguration.java | 8 +- .../vpc/dao/MockConfigurationDaoImpl.java | 1 - .../test/resources/createNetworkOffering.xml | 2 + .../SecondaryStorageManagerImpl.java | 2 +- .../integration/smoke/test_global_settings.py | 106 +++++ tools/marvin/marvin/lib/base.py | 8 + ui/public/locales/en.json | 4 +- ui/src/components/view/ListView.vue | 1 + ui/src/config/section/config.js | 4 +- ui/src/style/vars.less | 4 + .../views/setting/ConfigurationHierarchy.vue | 90 +++++ ui/src/views/setting/ConfigurationTab.vue | 365 ++++++++++++++++++ ui/src/views/setting/ConfigurationTable.vue | 191 +++++++++ ui/src/views/setting/ConfigurationValue.vue | 363 +++++++++++++++++ .../usage/UsageManagerTestConfiguration.java | 4 +- 81 files changed, 3249 insertions(+), 172 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgGroupsByCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/ConfigurationGroupResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/ConfigurationSubGroupResponse.java create mode 100644 engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java create mode 100644 framework/config/src/main/java/org/apache/cloudstack/config/ConfigurationGroup.java create mode 100644 framework/config/src/main/java/org/apache/cloudstack/config/ConfigurationSubGroup.java create mode 100644 framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationGroupDao.java create mode 100644 framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationGroupDaoImpl.java create mode 100644 framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationSubGroupDao.java create mode 100644 framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationSubGroupDaoImpl.java create mode 100644 framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationGroupVO.java create mode 100644 framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationSubGroupVO.java create mode 100644 ui/src/views/setting/ConfigurationHierarchy.vue create mode 100644 ui/src/views/setting/ConfigurationTab.vue create mode 100644 ui/src/views/setting/ConfigurationTable.vue create mode 100644 ui/src/views/setting/ConfigurationValue.vue diff --git a/api/src/main/java/com/cloud/server/ManagementService.java b/api/src/main/java/com/cloud/server/ManagementService.java index e1b9705ecf5..df44ec67019 100644 --- a/api/src/main/java/com/cloud/server/ManagementService.java +++ b/api/src/main/java/com/cloud/server/ManagementService.java @@ -22,6 +22,7 @@ import java.util.Map; import com.cloud.user.UserData; import org.apache.cloudstack.api.command.admin.cluster.ListClustersCmd; +import org.apache.cloudstack.api.command.admin.config.ListCfgGroupsByCmd; import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd; import org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCmd; @@ -63,6 +64,7 @@ import org.apache.cloudstack.api.command.user.userdata.RegisterUserDataCmd; import org.apache.cloudstack.api.command.user.vm.GetVMPasswordCmd; import org.apache.cloudstack.api.command.user.vmgroup.UpdateVMGroupCmd; import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.config.ConfigurationGroup; import com.cloud.alert.Alert; import com.cloud.capacity.Capacity; @@ -102,6 +104,13 @@ public interface ManagementService { */ Pair, Integer> searchForConfigurations(ListCfgsByCmd c); + /** + * returns the the configuration groups + * + * @return list of configuration groups + */ + Pair, Integer> listConfigurationGroups(ListCfgGroupsByCmd cmd); + /** * Searches for Clusters by the specified search criteria * 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 9548ddfc6b4..e655e90dc52 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -118,6 +118,7 @@ public class ApiConstants { public static final String MAX_IOPS = "maxiops"; public static final String HYPERVISOR_SNAPSHOT_RESERVE = "hypervisorsnapshotreserve"; public static final String DATADISK_OFFERING_LIST = "datadiskofferinglist"; + public static final String DEFAULT_VALUE = "defaultvalue"; public static final String DESCRIPTION = "description"; public static final String DESTINATION = "destination"; public static final String DESTINATION_ZONE_ID = "destzoneid"; @@ -187,6 +188,7 @@ public class ApiConstants { public static final String GATEWAY = "gateway"; public static final String IP6_GATEWAY = "ip6gateway"; public static final String GROUP = "group"; + public static final String SUBGROUP = "subgroup"; public static final String GROUP_ID = "groupid"; public static final String GSLB_LB_METHOD = "gslblbmethod"; public static final String GSLB_SERVICE_DOMAIN_NAME = "gslbdomainname"; @@ -301,6 +303,7 @@ public class ApiConstants { public static final String IS_DEFAULT_USE = "defaultuse"; public static final String OLD_FORMAT = "oldformat"; public static final String OP = "op"; + public static final String OPTIONS = "options"; public static final String OS_CATEGORY_ID = "oscategoryid"; public static final String OS_CATEGORY_NAME = "oscategoryname"; public static final String OS_ID = "osid"; @@ -312,6 +315,7 @@ public class ApiConstants { public static final String OUTPUT = "output"; public static final String PROPERTIES = "properties"; public static final String PARAMS = "params"; + public static final String PARENT = "parent"; public static final String PARENT_ID = "parentid"; public static final String PARENT_DOMAIN_ID = "parentdomainid"; public static final String PARENT_TEMPLATE_ID = "parenttemplateid"; @@ -333,6 +337,7 @@ public class ApiConstants { public static final String POSITION = "position"; public static final String POST_URL = "postURL"; public static final String POWER_STATE = "powerstate"; + public static final String PRECEDENCE = "precedence"; public static final String PRIVATE_INTERFACE = "privateinterface"; public static final String PRIVATE_IP = "privateip"; public static final String PRIVATE_PORT = "privateport"; diff --git a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java index a90c222e351..31f06dfc262 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -40,6 +40,7 @@ import org.apache.cloudstack.api.response.BackupScheduleResponse; import org.apache.cloudstack.api.response.CapacityResponse; import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.ConditionResponse; +import org.apache.cloudstack.api.response.ConfigurationGroupResponse; import org.apache.cloudstack.api.response.ConfigurationResponse; import org.apache.cloudstack.api.response.CounterResponse; import org.apache.cloudstack.api.response.CreateCmdResponse; @@ -133,6 +134,7 @@ import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.config.ConfigurationGroup; import org.apache.cloudstack.direct.download.DirectDownloadCertificate; import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap; import org.apache.cloudstack.direct.download.DirectDownloadManager; @@ -244,6 +246,8 @@ public interface ResponseGenerator { ConfigurationResponse createConfigurationResponse(Configuration cfg); + ConfigurationGroupResponse createConfigurationGroupResponse(ConfigurationGroup cfgGroup); + SnapshotResponse createSnapshotResponse(Snapshot snapshot); SnapshotPolicyResponse createSnapshotPolicyResponse(SnapshotPolicy policy); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgGroupsByCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgGroupsByCmd.java new file mode 100644 index 00000000000..46ab10cb2bc --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgGroupsByCmd.java @@ -0,0 +1,79 @@ +// 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.config; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ConfigurationGroupResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.config.ConfigurationGroup; +import org.apache.log4j.Logger; + +import com.cloud.utils.Pair; + +@APICommand(name = ListCfgGroupsByCmd.APINAME, description = "Lists all configuration groups (primarily used for UI).", responseObject = ConfigurationGroupResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.18.0") +public class ListCfgGroupsByCmd extends BaseListCmd { + public static final Logger s_logger = Logger.getLogger(ListCfgGroupsByCmd.class.getName()); + + public static final String APINAME = "listConfigurationGroups"; + + // /////////////////////////////////////////////////// + // ////////////// API parameters ///////////////////// + // /////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.GROUP, type = CommandType.STRING, description = "lists configuration group by group name") + private String groupName; + + // /////////////////////////////////////////////////// + // ///////////////// Accessors /////////////////////// + // /////////////////////////////////////////////////// + + public String getGroupName() { + return groupName; + } + + // /////////////////////////////////////////////////// + // ///////////// API Implementation/////////////////// + // /////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public void execute() { + Pair, Integer> result = _mgr.listConfigurationGroups(this); + ListResponse response = new ListResponse<>(); + List configGroupResponses = new ArrayList<>(); + for (ConfigurationGroup cfgGroup : result.first()) { + ConfigurationGroupResponse cfgGroupResponse = _responseGenerator.createConfigurationGroupResponse(cfgGroup); + configGroupResponses.add(cfgGroupResponse); + } + + response.setResponses(configGroupResponses, result.second()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java index b47bb6b87d5..457aeabe2f8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java @@ -19,7 +19,10 @@ package org.apache.cloudstack.api.command.admin.config; import java.util.ArrayList; import java.util.List; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -35,7 +38,9 @@ import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.config.Configuration; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; @APICommand(name = "listConfigurations", description = "Lists all configurations.", responseObject = ConfigurationResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -89,6 +94,15 @@ public class ListCfgsByCmd extends BaseListCmd { description = "the ID of the Image Store to update the parameter value for corresponding image store") private Long imageStoreId; + @Parameter(name = ApiConstants.GROUP, type = CommandType.STRING, description = "lists configuration by group name (primarily used for UI)", since = "4.18.0") + private String groupName; + + @Parameter(name = ApiConstants.SUBGROUP, type = CommandType.STRING, description = "lists configuration by subgroup name (primarily used for UI)", since = "4.18.0") + private String subGroupName; + + @Parameter(name = ApiConstants.PARENT, type = CommandType.STRING, description = "lists configuration by parent name (primarily used for UI)", since = "4.18.0") + private String parentName; + // /////////////////////////////////////////////////// // ///////////////// Accessors /////////////////////// // /////////////////////////////////////////////////// @@ -125,6 +139,26 @@ public class ListCfgsByCmd extends BaseListCmd { return imageStoreId; } + public String getGroupName() { + return groupName; + } + + public String getSubGroupName() { + return subGroupName; + } + + public String getParentName() { + return parentName; + } + + @Override + public Integer getPageSize() { + if (StringUtils.isNotEmpty(getGroupName())) { + return Integer.valueOf(s_pageSizeUnlimited.intValue()); + } + return super.getPageSize(); + } + @Override public Long getPageSizeVal() { Long defaultPageSize = 500L; @@ -143,37 +177,74 @@ public class ListCfgsByCmd extends BaseListCmd { // ///////////// API Implementation/////////////////// // /////////////////////////////////////////////////// + private void setScope(ConfigurationResponse cfgResponse) { + if (!matchesConfigurationGroup(cfgResponse)) { + return; + } + cfgResponse.setObjectName("configuration"); + if (getZoneId() != null) { + cfgResponse.setScope("zone"); + } + if (getClusterId() != null) { + cfgResponse.setScope("cluster"); + } + if (getStoragepoolId() != null) { + cfgResponse.setScope("storagepool"); + } + if (getAccountId() != null) { + cfgResponse.setScope("account"); + } + if (getDomainId() != null) { + cfgResponse.setScope("domain"); + } + if (getImageStoreId() != null){ + cfgResponse.setScope("imagestore"); + } + } + @Override public void execute() { - Pair, Integer> result = _mgr.searchForConfigurations(this); - ListResponse response = new ListResponse(); - List configResponses = new ArrayList(); - for (Configuration cfg : result.first()) { - ConfigurationResponse cfgResponse = _responseGenerator.createConfigurationResponse(cfg); - cfgResponse.setObjectName("configuration"); - if (getZoneId() != null) { - cfgResponse.setScope("zone"); + validateParameters(); + try { + Pair, Integer> result = _mgr.searchForConfigurations(this); + ListResponse response = new ListResponse<>(); + List configResponses = new ArrayList<>(); + for (Configuration cfg : result.first()) { + ConfigurationResponse cfgResponse = _responseGenerator.createConfigurationResponse(cfg); + setScope(cfgResponse); + configResponses.add(cfgResponse); } - if (getClusterId() != null) { - cfgResponse.setScope("cluster"); - } - if (getStoragepoolId() != null) { - cfgResponse.setScope("storagepool"); - } - if (getAccountId() != null) { - cfgResponse.setScope("account"); - } - if (getDomainId() != null) { - cfgResponse.setScope("domain"); - } - if (getImageStoreId() != null){ - cfgResponse.setScope("imagestore"); - } - configResponses.add(cfgResponse); - } - response.setResponses(configResponses, result.second()); - response.setResponseName(getCommandName()); - setResponseObject(response); + if (StringUtils.isNotEmpty(getGroupName())) { + response.setResponses(configResponses, configResponses.size()); + } else { + response.setResponses(configResponses, result.second()); + } + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (InvalidParameterValueException e) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage()); + } catch (CloudRuntimeException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + private void validateParameters() { + if (StringUtils.isNotEmpty(getSubGroupName()) && StringUtils.isEmpty(getGroupName())) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Configuration group name must be specified with the subgroup name"); + } + } + + private boolean matchesConfigurationGroup(ConfigurationResponse cfgResponse) { + if (StringUtils.isNotEmpty(getGroupName())) { + if (!(getGroupName().equalsIgnoreCase(cfgResponse.getGroup()))) { + return false; + } + if (StringUtils.isNotEmpty(getSubGroupName()) && + !getSubGroupName().equalsIgnoreCase(cfgResponse.getSubGroup())) { + return false; + } + } + return true; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ConfigurationGroupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ConfigurationGroupResponse.java new file mode 100644 index 00000000000..053ee5fdc35 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ConfigurationGroupResponse.java @@ -0,0 +1,76 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; + +public class ConfigurationGroupResponse extends BaseResponse { + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the configuration group") + private String groupName; + + @SerializedName(ApiConstants.SUBGROUP) + @Param(description = "the subgroups of the configuration group", responseObject = ConfigurationSubGroupResponse.class) + private List subGroups; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "the description of the configuration group") + private String description; + + @SerializedName(ApiConstants.PRECEDENCE) + @Param(description = "the precedence of the configuration group") + private Long precedence; + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public List getSubGroups() { + return subGroups; + } + + public void setSubGroups(List subGroups) { + this.subGroups = subGroups; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Long getPrecedence() { + return precedence; + } + + public void setPrecedence(Long precedence) { + this.precedence = precedence; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ConfigurationResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ConfigurationResponse.java index c42307c265a..1818e914a97 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ConfigurationResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ConfigurationResponse.java @@ -28,6 +28,14 @@ public class ConfigurationResponse extends BaseResponse { @Param(description = "the category of the configuration") private String category; + @SerializedName(ApiConstants.GROUP) + @Param(description = "the group of the configuration", since = "4.18.0") + private String group; + + @SerializedName(ApiConstants.SUBGROUP) + @Param(description = "the subgroup of the configuration", since = "4.18.0") + private String subGroup; + @SerializedName(ApiConstants.NAME) @Param(description = "the name of the configuration") private String name; @@ -36,6 +44,10 @@ public class ConfigurationResponse extends BaseResponse { @Param(description = "the value of the configuration") private String value; + @SerializedName(ApiConstants.DEFAULT_VALUE) + @Param(description = "the default value of the configuration", since = "4.18.0") + private String defaultValue; + @SerializedName(ApiConstants.SCOPE) @Param(description = "scope(zone/cluster/pool/account) of the parameter that needs to be updated") private String scope; @@ -52,6 +64,26 @@ public class ConfigurationResponse extends BaseResponse { @Param(description = "true if the configuration is dynamic") private boolean isDynamic; + @SerializedName(ApiConstants.COMPONENT) + @Param(description = "the component of the configuration", since = "4.18.0") + private String component; + + @SerializedName(ApiConstants.PARENT) + @Param(description = "the name of the parent configuration", since = "4.18.0") + private String parent; + + @SerializedName(ApiConstants.DISPLAY_TEXT) + @Param(description = "the display text of the configuration", since = "4.18.0") + private String displayText; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "the type of the configuration value", since = "4.18.0") + private String type; + + @SerializedName(ApiConstants.OPTIONS) + @Param(description = "the possible options of the configuration value", since = "4.18.0") + private String options; + public String getCategory() { return category; } @@ -60,6 +92,22 @@ public class ConfigurationResponse extends BaseResponse { this.category = category; } + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getSubGroup() { + return subGroup; + } + + public void setSubGroup(String subGroup) { + this.subGroup = subGroup; + } + public String getName() { return name; } @@ -76,6 +124,14 @@ public class ConfigurationResponse extends BaseResponse { this.value = value; } + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + public String getDescription() { return description; } @@ -100,4 +156,44 @@ public class ConfigurationResponse extends BaseResponse { this.isDynamic = isDynamic; } + public String getComponent() { + return component; + } + + public void setComponent(String component) { + this.component = component; + } + + public String getParent() { + return parent; + } + + public void setParent(String parent) { + this.parent = parent; + } + + public String getDisplayText() { + return displayText; + } + + public void setDisplayText(String displayText) { + this.displayText = displayText; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getOptions() { + return options; + } + + public void setOptions(String options) { + this.options = options; + } + } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ConfigurationSubGroupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ConfigurationSubGroupResponse.java new file mode 100644 index 00000000000..fda8e18b361 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ConfigurationSubGroupResponse.java @@ -0,0 +1,50 @@ +// 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.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; + +public class ConfigurationSubGroupResponse extends BaseResponse { + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the configuration subgroup") + private String subGroupName; + + @SerializedName(ApiConstants.PRECEDENCE) + @Param(description = "the precedence of the configuration subgroup") + private Long precedence; + + public String getSubGroupName() { + return subGroupName; + } + + public void setSubGroupName(String subGroupName) { + this.subGroupName = subGroupName; + } + + public Long getPrecedence() { + return precedence; + } + + public void setPrecedence(Long precedence) { + this.precedence = precedence; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java index 43b70fa7665..7b39804c738 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -44,12 +44,12 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer ConfigKey BackupProviderPlugin = new ConfigKey<>("Advanced", String.class, "backup.framework.provider.plugin", "dummy", - "The backup and recovery provider plugin.", true, ConfigKey.Scope.Zone); + "The backup and recovery provider plugin.", true, ConfigKey.Scope.Zone, BackupFrameworkEnabled.key()); ConfigKey BackupSyncPollingInterval = new ConfigKey<>("Advanced", Long.class, "backup.framework.sync.interval", "300", - "The backup and recovery background sync task polling interval in seconds.", true); + "The backup and recovery background sync task polling interval in seconds.", true, BackupFrameworkEnabled.key()); ConfigKey BackupEnableAttachDetachVolumes = new ConfigKey<>("Advanced", Boolean.class, "backup.enable.attach.detach.of.volumes", diff --git a/api/src/main/java/org/apache/cloudstack/ca/CAManager.java b/api/src/main/java/org/apache/cloudstack/ca/CAManager.java index dbabfa2a484..12a9d3d7b41 100644 --- a/api/src/main/java/org/apache/cloudstack/ca/CAManager.java +++ b/api/src/main/java/org/apache/cloudstack/ca/CAManager.java @@ -46,10 +46,10 @@ public interface CAManager extends CAService, Configurable, PluggableService { "2048", "The key size to be used for random certificate keypair generation.", true); - ConfigKey CertSignatureAlgorithm = new ConfigKey<>("Advanced", String.class, - "ca.framework.cert.signature.algorithm", + ConfigKey CertSignatureAlgorithm = new ConfigKey<>(String.class, + "ca.framework.cert.signature.algorithm", "Advanced", "SHA256withRSA", - "The default signature algorithm to use for certificate generation.", true); + "The default signature algorithm to use for certificate generation.", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, "SHA256withRSA"); ConfigKey CertValidityPeriod = new ConfigKey<>("Advanced", Integer.class, diff --git a/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java b/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java index 36458820870..a4aa860487f 100644 --- a/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java +++ b/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java @@ -20,15 +20,15 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; public class ApiServiceConfiguration implements Configurable { - public static final ConfigKey ManagementServerAddresses = new ConfigKey("Advanced", String.class, "host", "localhost", "The ip address of management server. This can also accept comma separated addresses.", true); + public static final ConfigKey ManagementServerAddresses = new ConfigKey<>(String.class, "host", "Advanced", "localhost", "The ip address of management server. This can also accept comma separated addresses.", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null); public static final ConfigKey ApiServletPath = new ConfigKey("Advanced", String.class, "endpoint.url", "http://localhost:8080/client/api", "API end point. Can be used by CS components/services deployed remotely, for sending CS API requests", true); public static final ConfigKey DefaultUIPageSize = new ConfigKey("Advanced", Long.class, "default.ui.page.size", "20", "The default pagesize to be used by UI and other clients when making list* API calls", true, ConfigKey.Scope.Global); public static final ConfigKey ApiSourceCidrChecksEnabled = new ConfigKey<>("Advanced", Boolean.class, "api.source.cidr.checks.enabled", "true", "Are the source checks on API calls enabled (true) or not (false)? See api.allowed.source.cidr.list", true, ConfigKey.Scope.Global); - public static final ConfigKey ApiAllowedSourceCidrList = new ConfigKey("Advanced", String.class, "api.allowed.source.cidr.list", - "0.0.0.0/0,::/0", "Comma separated list of IPv4/IPv6 CIDRs from which API calls can be performed. Can be set on Global and Account levels.", true, ConfigKey.Scope.Account); + public static final ConfigKey ApiAllowedSourceCidrList = new ConfigKey<>(String.class, "api.allowed.source.cidr.list", "Advanced", + "0.0.0.0/0,::/0", "Comma separated list of IPv4/IPv6 CIDRs from which API calls can be performed. Can be set on Global and Account levels.", true, ConfigKey.Scope.Account, null, null, null, null, null, ConfigKey.Kind.CSV, null); @Override public String getConfigComponentName() { return ApiServiceConfiguration.class.getSimpleName(); diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java b/api/src/main/java/org/apache/cloudstack/query/QueryService.java index 231e0f2cbd1..0587294a826 100644 --- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java +++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java @@ -94,13 +94,13 @@ public interface QueryService { ConfigKey AllowUserViewDestroyedVM = new ConfigKey<>("Advanced", Boolean.class, "allow.user.view.destroyed.vm", "false", "Determines whether users can view their destroyed or expunging vm ", true, ConfigKey.Scope.Account); - static final ConfigKey UserVMDeniedDetails = new ConfigKey("Advanced", String.class, - "user.vm.denied.details", "rootdisksize, cpuOvercommitRatio, memoryOvercommitRatio, Message.ReservedCapacityFreed.Flag", - "Determines whether users can view certain VM settings. When set to empty, default value used is: rootdisksize, cpuOvercommitRatio, memoryOvercommitRatio, Message.ReservedCapacityFreed.Flag.", true); + static final ConfigKey UserVMDeniedDetails = new ConfigKey<>(String.class, + "user.vm.denied.details", "Advanced", "rootdisksize, cpuOvercommitRatio, memoryOvercommitRatio, Message.ReservedCapacityFreed.Flag", + "Determines whether users can view certain VM settings. When set to empty, default value used is: rootdisksize, cpuOvercommitRatio, memoryOvercommitRatio, Message.ReservedCapacityFreed.Flag.", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null); - static final ConfigKey UserVMReadOnlyDetails = new ConfigKey("Advanced", String.class, - "user.vm.readonly.details", "dataDiskController, rootDiskController", - "List of read-only VM settings/details as comma separated string", true); + static final ConfigKey UserVMReadOnlyDetails = new ConfigKey<>(String.class, + "user.vm.readonly.details", "Advanced", "dataDiskController, rootDiskController", + "List of read-only VM settings/details as comma separated string", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null); ConfigKey SortKeyAscending = new ConfigKey<>("Advanced", Boolean.class, "sortkey.algorithm", "true", "Sort algorithm - ascending or descending - to use. For entities that use sort key(template, disk offering, service offering, " + diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 4ffb30b493a..ada94cd057c 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -66,7 +66,7 @@ public interface VirtualMachineManager extends Manager { "If config drive need to be created and hosted on primary storage pool. Currently only supported for KVM.", true, ConfigKey.Scope.Zone); ConfigKey VmConfigDriveUseHostCacheOnUnsupportedPool = new ConfigKey<>("Advanced", Boolean.class, "vm.configdrive.use.host.cache.on.unsupported.pool", "true", - "If true, config drive is created on the host cache storage when vm.configdrive.primarypool.enabled is true and the primary pool type doesn't support config drive.", true, ConfigKey.Scope.Zone); + "If true, config drive is created on the host cache storage when vm.configdrive.primarypool.enabled is true and the primary pool type doesn't support config drive.", true, ConfigKey.Scope.Zone, VmConfigDriveOnPrimaryPool.key()); ConfigKey VmConfigDriveForceHostCacheUse = new ConfigKey<>("Advanced", Boolean.class, "vm.configdrive.force.host.cache.use", "false", "If true, config drive is forced to create on the host cache storage. Currently only supported for KVM.", true, ConfigKey.Scope.Zone); diff --git a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java index cff225541d6..10b53082bac 100644 --- a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java +++ b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.cloudstack.framework.config.impl.ConfigurationSubGroupVO; + import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter.NetworkType; @@ -42,6 +44,7 @@ import com.cloud.offering.NetworkOffering.Availability; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.org.Grouping.AllocationState; import com.cloud.user.Account; +import com.cloud.utils.Pair; import com.cloud.utils.net.NetUtils; /** @@ -261,4 +264,10 @@ public interface ConfigurationManager { AllocationState findPodAllocationState(HostPodVO pod); AllocationState findClusterAllocationState(ClusterVO cluster); + + String getConfigurationType(String configName); + + Pair getConfigurationGroupAndSubGroup(String configName); + + List getConfigurationSubGroups(Long groupId); } diff --git a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java index 61489e5f7c8..32d5aa8dfa2 100644 --- a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java @@ -49,11 +49,12 @@ public interface IpAddressManager { "When true, ip address delete (ipassoc) failures are ignored", true); ConfigKey VrouterRedundantTiersPlacement = new ConfigKey( - "Advanced", String.class, + String.class, "vrouter.redundant.tiers.placement", + "Advanced", "random", "Set placement of vrouter ips in redundant mode in vpc tiers, this can be 3 value: `first` to use first ips in tiers, `last` to use last ips in tiers and `random` to take random ips in tiers.", - true, ConfigKey.Scope.Account); + true, ConfigKey.Scope.Account, null, null, null, null, null, ConfigKey.Kind.Select, "first,last,random"); /** * Assigns a new public ip address. diff --git a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java index 2857bbc9c44..9308be5fb32 100755 --- a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java +++ b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java @@ -53,13 +53,13 @@ public interface ResourceManager extends ResourceService, Configurable { "Number of retries when preparing a host into Maintenance Mode is faulty before failing", false); - ConfigKey HOST_MAINTENANCE_LOCAL_STRATEGY = new ConfigKey<>("Advanced", String.class, - "host.maintenance.local.storage.strategy", "Error", + ConfigKey HOST_MAINTENANCE_LOCAL_STRATEGY = new ConfigKey<>(String.class, + "host.maintenance.local.storage.strategy", "Advanced","Error", "Defines the strategy towards VMs with volumes on local storage when putting a host in maintenance. " + "The default strategy is 'Error', preventing maintenance in such a case. " + "Choose 'Migration' strategy to migrate away VMs running on local storage. " + "To force-stop VMs, choose 'ForceStop' strategy", - true, ConfigKey.Scope.Global); + true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, "Error,Migration,ForceStop"); /** * Register a listener for different types of resource life cycle events. diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java index e332858e787..8437b56f2e0 100644 --- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java +++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java @@ -45,22 +45,6 @@ import com.cloud.vm.DiskProfile; import com.cloud.vm.VMInstanceVO; public interface StorageManager extends StorageService { - ConfigKey StorageCleanupInterval = new ConfigKey<>(Integer.class, - "storage.cleanup.interval", - "Advanced", - "86400", - "The interval (in seconds) to wait before running the storage cleanup thread.", - false, - ConfigKey.Scope.Global, - null); - ConfigKey StorageCleanupDelay = new ConfigKey<>(Integer.class, - "storage.cleanup.delay", - "Advanced", - "86400", - "Determines how long (in seconds) to wait before actually expunging destroyed volumes. The default value = the default value of storage.cleanup.interval.", - false, - ConfigKey.Scope.Global, - null); ConfigKey StorageCleanupEnabled = new ConfigKey<>(Boolean.class, "storage.cleanup.enabled", "Advanced", @@ -69,6 +53,24 @@ public interface StorageManager extends StorageService { false, ConfigKey.Scope.Global, null); + ConfigKey StorageCleanupInterval = new ConfigKey<>(Integer.class, + "storage.cleanup.interval", + "Advanced", + "86400", + "The interval (in seconds) to wait before running the storage cleanup thread.", + false, + ConfigKey.Scope.Global, + null, + StorageCleanupEnabled.key()); + ConfigKey StorageCleanupDelay = new ConfigKey<>(Integer.class, + "storage.cleanup.delay", + "Advanced", + "86400", + "Determines how long (in seconds) to wait before actually expunging destroyed volumes. The default value = the default value of storage.cleanup.interval.", + false, + ConfigKey.Scope.Global, + null, + StorageCleanupEnabled.key()); ConfigKey TemplateCleanupEnabled = new ConfigKey<>(Boolean.class, "storage.template.cleanup.enabled", "Storage", @@ -76,7 +78,8 @@ public interface StorageManager extends StorageService { "Enable/disable template cleanup activity, only take effect when overall storage cleanup is enabled", false, ConfigKey.Scope.Global, - null); + null, + StorageCleanupEnabled.key()); ConfigKey KvmStorageOfflineMigrationWait = new ConfigKey<>(Integer.class, "kvm.storage.offline.migration.wait", "Storage", diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java index cc8305c5723..749a738e63e 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java @@ -136,7 +136,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust protected final ConfigKey EnableLB = new ConfigKey(Boolean.class, "agent.lb.enabled", "Advanced", "false", "Enable agent load balancing between management server nodes", true); protected final ConfigKey ConnectedAgentThreshold = new ConfigKey(Double.class, "agent.load.threshold", "Advanced", "0.7", - "What percentage of the agents can be held by one management server before load balancing happens", true); + "What percentage of the agents can be held by one management server before load balancing happens", true, EnableLB.key()); protected final ConfigKey LoadSize = new ConfigKey(Integer.class, "direct.agent.load.size", "Advanced", "16", "How many agents to connect to in each round", true); protected final ConfigKey ScanInterval = new ConfigKey(Integer.class, "direct.agent.scan.interval", "Advanced", "90", "Interval between scans to load agents", false, ConfigKey.Scope.Global, 1000); diff --git a/engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java b/engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java new file mode 100644 index 00000000000..34de1bccb82 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.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.upgrade; + +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl; +import org.apache.cloudstack.framework.config.impl.ConfigurationSubGroupVO; +import org.apache.cloudstack.framework.config.impl.ConfigurationVO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import com.cloud.utils.Pair; + +public class ConfigurationGroupsAggregator { + + static final Logger LOG = Logger.getLogger(ConfigurationGroupsAggregator.class); + + @Inject + ConfigurationDao configDao; + @Inject + ConfigurationGroupDao configGroupDao; + @Inject + ConfigurationSubGroupDao configSubGroupDao; + + public ConfigurationGroupsAggregator() { + configDao = new ConfigurationDaoImpl(); + configGroupDao = new ConfigurationGroupDaoImpl(); + configSubGroupDao = new ConfigurationSubGroupDaoImpl(); + } + + public void updateConfigurationGroups() { + LOG.debug("Updating configuration groups"); + List configs = configDao.listAllIncludingRemoved(); + if (CollectionUtils.isEmpty(configs)) { + return; + } + + for (final ConfigurationVO config : configs) { + String configName = config.getName(); + if (StringUtils.isBlank(configName)) { + continue; + } + + try { + Pair configGroupAndSubGroup = getConfigurationGroupAndSubGroupByName(configName); + if (configGroupAndSubGroup.first() != 1 && configGroupAndSubGroup.second() != 1) { + config.setGroupId(configGroupAndSubGroup.first()); + config.setSubGroupId(configGroupAndSubGroup.second()); + configDao.persist(config); + } + } catch (Exception e) { + LOG.error("Failed to update group for configuration " + configName + " due to " + e.getMessage(), e); + } + } + LOG.debug("Successfully updated configuration groups."); + } + + private Pair getConfigurationGroupAndSubGroupByName(String configName) { + Long subGroupId = 1L; + Long groupId = 1L; + if (StringUtils.isNotBlank(configName)) { + // Get words from the dot notation in the configuration + String[] nameWords = configName.split("\\."); + if (nameWords.length > 0) { + for (int index = 0; index < nameWords.length; index++) { + ConfigurationSubGroupVO configSubGroup = configSubGroupDao.findByName(nameWords[index]); + + if (configSubGroup == null) { + configSubGroup = configSubGroupDao.findByKeyword(nameWords[index]); + } + + if (configSubGroup != null) { + subGroupId = configSubGroup.getId(); + groupId = configSubGroup.getGroupId(); + break; + } + } + } + } + + return new Pair<>(groupId, subGroupId); + } +} diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41610to41700.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41610to41700.java index 98497512fce..bb4e70567c6 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41610to41700.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41610to41700.java @@ -16,11 +16,6 @@ // under the License. package com.cloud.upgrade.dao; -import com.cloud.upgrade.SystemVmTemplateRegistration; -import com.cloud.utils.exception.CloudRuntimeException; - -import org.apache.log4j.Logger; - import java.io.InputStream; import java.math.BigInteger; import java.sql.Connection; @@ -29,6 +24,11 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.UUID; +import org.apache.log4j.Logger; + +import com.cloud.upgrade.SystemVmTemplateRegistration; +import com.cloud.utils.exception.CloudRuntimeException; + public class Upgrade41610to41700 implements DbUpgrade, DbUpgradeSystemVmTemplate { final static Logger LOG = Logger.getLogger(Upgrade41700to41710.class); diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41720to41800.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41720to41800.java index 468e4ec8f43..c24da883506 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41720to41800.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41720to41800.java @@ -17,6 +17,7 @@ package com.cloud.upgrade.dao; import com.cloud.storage.GuestOSHypervisorMapping; +import com.cloud.upgrade.ConfigurationGroupsAggregator; import com.cloud.upgrade.GuestOsMapper; import com.cloud.upgrade.SystemVmTemplateRegistration; import com.cloud.utils.exception.CloudRuntimeException; @@ -45,6 +46,7 @@ public class Upgrade41720to41800 implements DbUpgrade, DbUpgradeSystemVmTemplate private GuestOsMapper guestOsMapper = new GuestOsMapper(); private SystemVmTemplateRegistration systemVmTemplateRegistration; + private ConfigurationGroupsAggregator configGroupsAggregator = new ConfigurationGroupsAggregator(); @Override public String[] getUpgradableVersionRange() { @@ -79,6 +81,7 @@ public class Upgrade41720to41800 implements DbUpgrade, DbUpgradeSystemVmTemplate correctGuestOsNames(); updateGuestOsMappings(); correctGuestOsIdsInHypervisorMapping(conn); + updateConfigurationGroups(); } @Override @@ -702,4 +705,8 @@ public class Upgrade41720to41800 implements DbUpgrade, DbUpgradeSystemVmTemplate LOG.debug("Correcting guest OS ids in hypervisor mappings"); guestOsMapper.updateGuestOsIdInHypervisorMapping(conn, 10, "Ubuntu 20.04 LTS", new GuestOSHypervisorMapping("Xenserver", "8.2.0", "Ubuntu Focal Fossa 20.04")); } + + private void updateConfigurationGroups() { + configGroupsAggregator.updateConfigurationGroups(); + } } 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 ebda8235fa4..3f97ed90d15 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 @@ -61,6 +61,8 @@ + + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql index 68c2e344fa7..c259c1414d1 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql @@ -26,8 +26,6 @@ ALTER TABLE cloud.remote_access_vpn MODIFY ipsec_psk text NOT NULL; -- PR#5832 Fix 'endpointe.url' global settings configuration typo. UPDATE `cloud`.`configuration` SET name='endpoint.url' WHERE name='endpointe.url'; - - ALTER TABLE `cloud`.`service_offering` ADD COLUMN `uuid` varchar(40) UNIQUE DEFAULT NULL; ALTER TABLE `cloud`.`service_offering` ADD COLUMN `name` varchar(255) NOT NULL; ALTER TABLE `cloud`.`service_offering` ADD COLUMN `display_text` varchar(4096) DEFAULT NULL ; @@ -592,7 +590,7 @@ CREATE TABLE `cloud`.`vm_stats` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- PR#5984 Update name for global configuration vm.stats.increment.metrics -Update configuration set name='vm.stats.increment.metrics' where name='vm.stats.increment.metrics.in.memory'; +UPDATE `cloud`.`configuration` SET name = 'vm.stats.increment.metrics' WHERE name = 'vm.stats.increment.metrics.in.memory'; ALTER TABLE `cloud`.`domain_router` ADD COLUMN `software_version` varchar(100) COMMENT 'Software version'; @@ -970,4 +968,4 @@ WHERE not exists( SELECT 1 ;END; CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (2, 'Debian GNU/Linux 11 (64-bit)', 'XenServer', '8.2.1', 'Debian Bullseye 11'); -CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (2, 'Debian GNU/Linux 11 (32-bit)', 'XenServer', '8.2.1', 'Debian Bullseye 11'); +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (2, 'Debian GNU/Linux 11 (32-bit)', 'XenServer', '8.2.1', 'Debian Bullseye 11'); \ No newline at end of file diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql b/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql index 056a47ba512..2c078e37c03 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41720to41800.sql @@ -219,12 +219,12 @@ CREATE TABLE IF NOT EXISTS `cloud`.`passphrase` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- Add passphrase column to volumes table -CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'passphrase_id', 'bigint unsigned DEFAULT NULL COMMENT ''encryption passphrase id'' '); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'passphrase_id', 'bigint unsigned DEFAULT NULL COMMENT "encryption passphrase id" '); CALL `cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.volumes', 'passphrase', 'id'); -CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'encrypt_format', 'varchar(64) DEFAULT NULL COMMENT ''encryption format'' '); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'encrypt_format', 'varchar(64) DEFAULT NULL COMMENT "encryption format" '); -- Add encrypt column to disk_offering -CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.disk_offering', 'encrypt', 'tinyint(1) DEFAULT 0 COMMENT ''volume encrypt requested'' '); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.disk_offering', 'encrypt', 'tinyint(1) DEFAULT 0 COMMENT "volume encrypt requested" '); -- add encryption support to disk offering view DROP VIEW IF EXISTS `cloud`.`disk_offering_view`; @@ -1101,6 +1101,249 @@ INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`) SELECT UUID(), `roles`.`id`, 'isAccountAllowedToCreateOfferingsWithTags', 'ALLOW' FROM `cloud`.`roles` WHERE `role_type` = 'DomainAdmin'; +-- +-- Update Configuration Groups and Subgroups +-- +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.configuration', 'group_id', 'bigint unsigned DEFAULT 1 COMMENT "group id this configuration belongs to" '); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.configuration', 'subgroup_id', 'bigint unsigned DEFAULT 1 COMMENT "subgroup id this configuration belongs to" '); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.configuration', 'parent', 'VARCHAR(255) DEFAULT NULL COMMENT "name of the parent configuration if this depends on it" '); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.configuration', 'display_text', 'VARCHAR(255) DEFAULT NULL COMMENT "Short text about configuration to display to the users" '); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.configuration', 'kind', 'VARCHAR(255) DEFAULT NULL COMMENT "kind of the value such as order, csv, etc" '); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.configuration', 'options', 'VARCHAR(255) DEFAULT NULL COMMENT "possible options for the value" '); + +CREATE TABLE `cloud`.`configuration_group` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `name` varchar(255) NOT NULL COMMENT 'name of the configuration group', + `description` varchar(1024) DEFAULT NULL COMMENT 'description of the configuration group', + `precedence` bigint(20) unsigned DEFAULT '999' COMMENT 'precedence for the configuration group', + PRIMARY KEY (`id`), + UNIQUE KEY (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `cloud`.`configuration_subgroup` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `name` varchar(255) NOT NULL COMMENT 'name of the configuration subgroup', + `keywords` varchar(4096) DEFAULT NULL COMMENT 'comma-separated keywords for the configuration subgroup', + `precedence` bigint(20) unsigned DEFAULT '999' COMMENT 'precedence for the configuration subgroup', + `group_id` bigint(20) unsigned NOT NULL COMMENT 'configuration group id', + PRIMARY KEY (`id`), + UNIQUE KEY (`name`, `group_id`), + CONSTRAINT `fk_configuration_subgroup__group_id` FOREIGN KEY (`group_id`) REFERENCES `configuration_group` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +ALTER TABLE `cloud`.`configuration_group` AUTO_INCREMENT=1; + +INSERT INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('Miscellaneous', 'Miscellaneous configuration', 999); +INSERT INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('Access', 'Identity and Access management configuration', 1); +INSERT INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('Compute', 'Compute configuration', 2); +INSERT INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('Storage', 'Storage configuration', 3); +INSERT INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('Network', 'Network configuration', 4); +INSERT INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('Hypervisor', 'Hypervisor specific configuration', 5); +INSERT INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('Management Server', 'Management Server configuration', 6); +INSERT INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('System VMs', 'System VMs related configuration', 7); +INSERT INTO `cloud`.`configuration_group` (`name`, `description`, `precedence`) VALUES ('Infrastructure', 'Infrastructure configuration', 8); + +ALTER TABLE `cloud`.`configuration_subgroup` AUTO_INCREMENT=1; + +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Others', NULL, 999, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Miscellaneous')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Account', NULL, 1, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Access')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Domain', NULL, 2, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Access')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Project', NULL, 3, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Access')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('LDAP', NULL, 4, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Access')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('SAML', 'saml2', 5, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Access')); + +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Virtual Machine', 'vm,instance,cpu,ssh,affinity', 1, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Compute')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Kubernetes', 'kubernetes', 2, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Compute')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('High Availability', 'ha', 3, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Compute')); + +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Images', 'template,iso', 1, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Storage')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Volume', 'disk,diskoffering', 2, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Storage')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Snapshot', NULL, 3, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Storage')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('VM Snapshot', 'vmsnapshot', 4, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Storage')); + +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Network', 'firewall,vlan,dns,globodns,ipaddress,cidr', 1, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Network')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('DHCP', 'externaldhcp', 2, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Network')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('VPC', NULL, 3, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Network')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('LoadBalancer', 'lb,gslb', 4, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Network')); + +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('API', NULL, 1, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Management Server')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Alerts', 'alert', 2, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Management Server')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Events', 'event', 3, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Management Server')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Security', 'secure,password,authenticators', 4, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Management Server')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Usage', NULL, 5, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Management Server')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Limits', 'capacity,delay,interval,workers', 6, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Management Server')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Jobs', 'job', 7, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Management Server')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Agent', NULL, 8, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Management Server')); + +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Hypervisor', 'host', 1, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Hypervisor')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('KVM', 'libvirt', 2, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Hypervisor')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('VMware', 'vcenter', 3, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Hypervisor')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('XenServer', 'xen,xapi,XCP', 4, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Hypervisor')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('OVM', 'ovm3', 5, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Hypervisor')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Baremetal', NULL, 6, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Hypervisor')); + +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('ConsoleProxyVM', 'cpvm,consoleproxy,novnc', 1, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'System VMs')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('SecStorageVM', 'ssvm,secondary', 2, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'System VMs')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('VirtualRouter', 'vr,router,vrouter', 3, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'System VMs')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Diagnostics', NULL, 4, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'System VMs')); + +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Primary Storage', 'storage,pool,primary', 1, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Infrastructure')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Secondary Storage', 'image,secstorage', 2, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Infrastructure')); + +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Backup & Recovery', 'backup,recovery,veeam', 1, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Miscellaneous')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Certificate Authority', 'CA', 2, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Miscellaneous')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Quota', NULL, 3, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Miscellaneous')); +INSERT INTO `cloud`.`configuration_subgroup` (`name`, `keywords`, `precedence`, `group_id`) VALUES ('Cloudian', NULL, 4, (SELECT id FROM `cloud`.`configuration_group` WHERE `name` = 'Miscellaneous')); + +UPDATE `cloud`.`configuration` SET parent = 'agent.lb.enabled' WHERE name IN ('agent.load.threshold'); +UPDATE `cloud`.`configuration` SET parent = 'indirect.agent.lb.check.interval' WHERE name IN ('indirect.agent.lb.algorithm'); +UPDATE `cloud`.`configuration` SET parent = 'alert.purge.delay' WHERE name IN ('alert.purge.interval'); +UPDATE `cloud`.`configuration` SET parent = 'api.throttling.enabled' WHERE name IN ('api.throttling.cachesize', 'api.throttling.interval', 'api.throttling.max'); +UPDATE `cloud`.`configuration` SET parent = 'backup.framework.enabled' WHERE name IN ('backup.framework.provider.plugin', 'backup.framework.sync.interval'); +UPDATE `cloud`.`configuration` SET parent = 'cloud.kubernetes.service.enabled' WHERE name IN ('cloud.kubernetes.cluster.max.size', 'cloud.kubernetes.cluster.network.offering', 'cloud.kubernetes.cluster.scale.timeout', 'cloud.kubernetes.cluster.start.timeout', 'cloud.kubernetes.cluster.upgrade.timeout', 'cloud.kubernetes.cluster.experimental.features.enabled'); +UPDATE `cloud`.`configuration` SET parent = 'diagnostics.data.gc.enable' WHERE name IN ('diagnostics.data.gc.interval', 'diagnostics.data.max.file.age'); +UPDATE `cloud`.`configuration` SET parent = 'enable.additional.vm.configuration' WHERE name IN ('allow.additional.vm.configuration.list.kvm', 'allow.additional.vm.configuration.list.vmware', 'allow.additional.vm.configuration.list.xenserver'); +UPDATE `cloud`.`configuration` SET parent = 'event.purge.delay' WHERE name IN ('event.purge.interval'); +UPDATE `cloud`.`configuration` SET parent = 'network.loadbalancer.basiczone.elb.enabled' WHERE name IN ('network.loadbalancer.basiczone.elb.network', 'network.loadbalancer.basiczone.elb.vm.cpu.mhz', 'network.loadbalancer.basiczone.elb.vm.ram.size', 'network.loadbalancer.basiczone.elb.vm.vcpu.num', 'network.loadbalancer.basiczone.elb.gc.interval.minutes'); +UPDATE `cloud`.`configuration` SET parent = 'prometheus.exporter.enable' WHERE name IN ('prometheus.exporter.port', 'prometheus.exporter.allowed.ips'); +UPDATE `cloud`.`configuration` SET parent = 'router.health.checks.enable' WHERE name IN ('router.health.checks.basic.interval', 'router.health.checks.advanced.interval', 'router.health.checks.config.refresh.interval', 'router.health.checks.results.fetch.interval', 'router.health.checks.to.exclude', 'router.health.checks.failures.to.recreate.vr', 'router.health.checks.free.disk.space.threshold', 'router.health.checks.max.cpu.usage.threshold', 'router.health.checks.max.memory.usage.threshold'); +UPDATE `cloud`.`configuration` SET parent = 'storage.cache.replacement.enabled' WHERE name IN ('storage.cache.replacement.interval', 'storage.cache.replacement.lru.interval'); +UPDATE `cloud`.`configuration` SET parent = 'storage.cleanup.enabled' WHERE name IN ('storage.cleanup.interval', 'storage.cleanup.delay', 'storage.template.cleanup.enabled'); +UPDATE `cloud`.`configuration` SET parent = 'vm.configdrive.primarypool.enabled' WHERE name IN ('vm.configdrive.use.host.cache.on.unsupported.pool'); + +UPDATE `cloud`.`configuration` SET display_text = CONCAT(UCASE(LEFT(REPLACE(name, ".", " "), 1)), LCASE(SUBSTRING(REPLACE(name, ".", " "), 2))); + +UPDATE `cloud`.`configuration` SET + `kind` = 'Order', + `options` = 'HostAntiAffinityProcessor,ExplicitDedicationProcessor,HostAffinityProcessor' +where `name` = 'affinity.processors.order' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Order', + `options` = 'FirstFitPlanner,UserDispersingPlanner,UserConcentratedPodPlanner,ImplicitDedicationPlanner,BareMetalPlanner' + where `name` = 'deployment.planners.order' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Order', + `options` = 'SimpleInvestigator,XenServerInvestigator,KVMInvestigator,HypervInvestigator,VMwareInvestigator,PingInvestigator,ManagementIPSysVMInvestigator,Ovm3Investigator' +where `name` = 'ha.investigators.order' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Order', + `options` = 'FirstFitRouting' +where `name` = 'host.allocators.order' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Order', + `options` = 'SAML2Auth' +where `name` = 'pluggableApi.authenticators.order' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Order', + `options` = 'AffinityGroupAccessChecker,DomainChecker' +where `name` = 'security.checkers.order' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Order', + `options` = 'LocalStorage,ClusterScopeStoragePoolAllocator,ZoneWideStoragePoolAllocator' +where `name` = 'storage.pool.allocators.order' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Order', + `options` = 'PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT' +where `name` = 'user.authenticators.order' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Order', + `options` = 'PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT' +where `name` = 'user.password.encoders.order' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'CSV' +where `name` like "%.list" ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'CSV' +where `name` like "%.defaults" ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'CSV' +where `name` like "%.details" ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'CSV' +where `name` like "%.exclude" ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'CSV' +where `name` IN ("alert.email.addresses", "allow.additional.vm.configuration.list.kvm", "allow.additional.vm.configuration.list.xenserver", "host", + "network.dhcp.nondefaultnetwork.setgateway.guestos", "router.health.checks.failures.to.recreate.vr", "router.health.checks.to.exclude") ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'Error,Migration,ForceStop' +where `name` = 'host.maintenance.local.storage.strategy' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'SHA256withRSA' +where `name` = 'ca.framework.cert.signature.algorithm' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'firstfitleastconsumed,random' +where `name` = 'image.store.allocation.algorithm' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'static,roundrobin,shuffle' +where `name` = 'indirect.agent.lb.algorithm' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'random,firstfit,userdispersing,userconcentratedpod_random,userconcentratedpod_firstfit,firstfitleastconsumed' +where `name` = 'vm.allocation.algorithm' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'all,pod' +where `name` = 'network.dns.basiczone.updates' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'global,guest-network,link-local,disabled,all,default' +where `name` = 'network.loadbalancer.haproxy.stats.visibility' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'SHA1,SHA256,SHA384,SHA512' +where `name` = 'saml2.sigalg' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'FirstFitPlanner,UserDispersingPlanner,UserConcentratedPodPlanner' +where `name` = 'vm.deployment.planner' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'scsi,ide,osdefault' +where `name` = 'vmware.root.disk.controller' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'E1000,PCNet32,Vmxnet2,Vmxnet3' +where `name` = 'vmware.systemvm.nic.device.type' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'first,last,random' +where `name` = 'vrouter.redundant.tiers.placement' ; + +UPDATE `cloud`.`configuration` SET + `kind` = 'Select', + `options` = 'xenserver56,xenserver61' +where `name` = 'xenserver.pvdriver.version' ; + --- Create table for handling console sessions #7094 CREATE TABLE IF NOT EXISTS `cloud`.`console_session` ( diff --git a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java index 1ca155cb7d9..41e83f4f7bb 100644 --- a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java +++ b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java @@ -65,8 +65,8 @@ public class ImageStoreProviderManagerImpl implements ImageStoreProviderManager, Map driverMaps; - static final ConfigKey ImageStoreAllocationAlgorithm = new ConfigKey("Advanced", String.class, "image.store.allocation.algorithm", "firstfitleastconsumed", - "firstfitleastconsumed','random' : Order in which hosts within a cluster will be considered for VM/volume allocation", true, ConfigKey.Scope.Global ); + static final ConfigKey ImageStoreAllocationAlgorithm = new ConfigKey<>(String.class, "image.store.allocation.algorithm", "Advanced", "firstfitleastconsumed", + "firstfitleastconsumed','random' : Order in which hosts within a cluster will be considered for VM/volume allocation", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, "firstfitleastconsumed,random" ); @PostConstruct public void config() { diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/ChildTestConfiguration.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/ChildTestConfiguration.java index 51156246b37..c5321ae6102 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/ChildTestConfiguration.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/ChildTestConfiguration.java @@ -33,6 +33,8 @@ import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationSer import org.apache.cloudstack.engine.service.api.OrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl; import org.apache.cloudstack.framework.rpc.RpcProvider; import org.apache.cloudstack.storage.cache.manager.StorageCacheManagerImpl; import org.apache.cloudstack.storage.test.ChildTestConfiguration.Library; @@ -96,12 +98,12 @@ import com.cloud.vm.snapshot.dao.VMSnapshotDaoImpl; @Configuration @ComponentScan(basePackageClasses = {NicDaoImpl.class, VMInstanceDaoImpl.class, VolumeDaoImpl.class, VMTemplatePoolDaoImpl.class, ResourceTagsDaoImpl.class, VMTemplateDaoImpl.class, MockStorageMotionStrategy.class, ConfigurationDaoImpl.class, - ClusterDaoImpl.class, HostPodDaoImpl.class, VMTemplateZoneDaoImpl.class, VMTemplateDetailsDaoImpl.class, HostDetailsDaoImpl.class, - HostTagsDaoImpl.class, HostTransferMapDaoImpl.class, DataCenterIpAddressDaoImpl.class, DataCenterLinkLocalIpAddressDaoImpl.class, - DataCenterVnetDaoImpl.class, PodVlanDaoImpl.class, DataCenterDetailsDaoImpl.class, DiskOfferingDaoImpl.class, StoragePoolHostDaoImpl.class, - UserVmDaoImpl.class, UserVmDetailsDaoImpl.class, ServiceOfferingDaoImpl.class, CapacityDaoImpl.class, SnapshotDaoImpl.class, VMSnapshotDaoImpl.class, - OCFS2ManagerImpl.class, ClusterDetailsDaoImpl.class, SecondaryStorageVmDaoImpl.class, ConsoleProxyDaoImpl.class, StoragePoolWorkDaoImpl.class, - StorageCacheManagerImpl.class, UserDaoImpl.class, DataCenterDaoImpl.class, StoragePoolDetailsDaoImpl.class, DomainDaoImpl.class, + ConfigurationGroupDaoImpl.class, ConfigurationSubGroupDaoImpl.class, ClusterDaoImpl.class, HostPodDaoImpl.class, VMTemplateZoneDaoImpl.class, + VMTemplateDetailsDaoImpl.class, HostDetailsDaoImpl.class, HostTagsDaoImpl.class, HostTransferMapDaoImpl.class, DataCenterIpAddressDaoImpl.class, + DataCenterLinkLocalIpAddressDaoImpl.class, DataCenterVnetDaoImpl.class, PodVlanDaoImpl.class, DataCenterDetailsDaoImpl.class, DiskOfferingDaoImpl.class, + StoragePoolHostDaoImpl.class, UserVmDaoImpl.class, UserVmDetailsDaoImpl.class, ServiceOfferingDaoImpl.class, CapacityDaoImpl.class, SnapshotDaoImpl.class, + VMSnapshotDaoImpl.class, OCFS2ManagerImpl.class, ClusterDetailsDaoImpl.class, SecondaryStorageVmDaoImpl.class, ConsoleProxyDaoImpl.class, + StoragePoolWorkDaoImpl.class, StorageCacheManagerImpl.class, UserDaoImpl.class, DataCenterDaoImpl.class, StoragePoolDetailsDaoImpl.class, DomainDaoImpl.class, DownloadMonitorImpl.class, AccountDaoImpl.class, ActionEventUtils.class, EventDaoImpl.class}, includeFilters = {@Filter(value = Library.class, type = FilterType.CUSTOM)}, useDefaultFilters = false) diff --git a/engine/storage/integration-test/src/test/resources/component.xml b/engine/storage/integration-test/src/test/resources/component.xml index 66a4aa80bab..aee37145114 100644 --- a/engine/storage/integration-test/src/test/resources/component.xml +++ b/engine/storage/integration-test/src/test/resources/component.xml @@ -188,5 +188,9 @@ --> - + + + + + diff --git a/framework/config/src/main/java/org/apache/cloudstack/config/Configuration.java b/framework/config/src/main/java/org/apache/cloudstack/config/Configuration.java index 19219305ca1..b93817a9919 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/config/Configuration.java +++ b/framework/config/src/main/java/org/apache/cloudstack/config/Configuration.java @@ -22,9 +22,17 @@ import java.util.Date; * Configuration represents one global configuration parameter for CloudStack. * Its scope should indicate whether this parameter can be set at different * organization levels in CloudStack. - * */ public interface Configuration { + enum ValueType { + Boolean, + Number, + Decimal, + Range, // Min and Max (Percentage - 0 to 100) + String, + List, // Set of values + Date; + } /** * @return Category of the parameter. @@ -83,8 +91,25 @@ public interface Configuration { Date getUpdated(); /** - * * @return returns true if the configuration is encrypted else false. */ boolean isEncrypted(); + + /** + * @return Parent of the configuration. + */ + String getParent(); + + /** + * @return Display text of the configuration. + */ + String getDisplayText(); + + Long getGroupId(); + + Long getSubGroupId(); + + String getKind(); + + String getOptions(); } diff --git a/framework/config/src/main/java/org/apache/cloudstack/config/ConfigurationGroup.java b/framework/config/src/main/java/org/apache/cloudstack/config/ConfigurationGroup.java new file mode 100644 index 00000000000..cd48e7f44de --- /dev/null +++ b/framework/config/src/main/java/org/apache/cloudstack/config/ConfigurationGroup.java @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.config; + +public interface ConfigurationGroup { + + long getId(); + + String getName(); + + String getDescription(); + + Long getPrecedence(); +} diff --git a/framework/config/src/main/java/org/apache/cloudstack/config/ConfigurationSubGroup.java b/framework/config/src/main/java/org/apache/cloudstack/config/ConfigurationSubGroup.java new file mode 100644 index 00000000000..833a04d7fb0 --- /dev/null +++ b/framework/config/src/main/java/org/apache/cloudstack/config/ConfigurationSubGroup.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.config; + +public interface ConfigurationSubGroup { + + public long getId(); + + public String getName(); + + public String getKeywords(); + + public Long getPrecedence(); + + public Long getGroupId(); +} diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigDepotAdmin.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigDepotAdmin.java index 444c718caea..894bc201a4c 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigDepotAdmin.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigDepotAdmin.java @@ -18,6 +18,8 @@ package org.apache.cloudstack.framework.config; import java.util.List; +import com.cloud.utils.Pair; + /** * Administrative interface to ConfigDepot * @@ -35,4 +37,6 @@ public interface ConfigDepotAdmin { void populateConfiguration(Configurable configurable); List getComponentsInDepot(); + + Pair getConfigurationGroupAndSubGroupByName(String configName); } diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java index 926e65cbd18..13c594f9367 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java @@ -21,6 +21,8 @@ import java.sql.Date; import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; import org.apache.cloudstack.framework.config.impl.ConfigurationVO; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; import com.cloud.utils.exception.CloudRuntimeException; /** @@ -33,10 +35,14 @@ public class ConfigKey { public static final String CATEGORY_ADVANCED = "Advanced"; public static final String CATEGORY_ALERT = "Alert"; - public static enum Scope { + public enum Scope { Global, Zone, Cluster, StoragePool, Account, ManagementServer, ImageStore, Domain } + public enum Kind { + CSV, Order, Select + } + private final String _category; public String category() { @@ -59,6 +65,10 @@ public class ConfigKey { return _description; } + public String displayText() { + return _displayText; + } + public Scope scope() { return _scope; } @@ -67,6 +77,26 @@ public class ConfigKey { return _isDynamic; } + public Ternary group() { + return _group; + } + + public Pair subGroup() { + return _subGroup; + } + + public final String parent() { + return _parent; + } + + public final Kind kind() { + return _kind; + } + + public final String options() { + return _options; + } + @Override public String toString() { return _name; @@ -76,8 +106,14 @@ public class ConfigKey { private final String _name; private final String _defaultValue; private final String _description; + private final String _displayText; private final Scope _scope; // Parameter can be at different levels (Zone/cluster/pool/account), by default every parameter is at global private final boolean _isDynamic; + private final String _parent; + private final Ternary _group; // Group name, description with precedence + private final Pair _subGroup; // SubGroup name with precedence + private final Kind _kind; // Kind such as order, csv, etc + private final String _options; // list of possible options in case of order, list, etc private final T _multiplier; T _value = null; @@ -91,19 +127,47 @@ public class ConfigKey { this(type, name, category, defaultValue, description, isDynamic, scope, null); } + public ConfigKey(String category, Class type, String name, String defaultValue, String description, boolean isDynamic, Scope scope, String parent) { + this(type, name, category, defaultValue, description, isDynamic, scope, null, null, parent, null, null, null, null); + } + public ConfigKey(String category, Class type, String name, String defaultValue, String description, boolean isDynamic) { this(type, name, category, defaultValue, description, isDynamic, Scope.Global, null); } + public ConfigKey(String category, Class type, String name, String defaultValue, String description, boolean isDynamic, String parent) { + this(type, name, category, defaultValue, description, isDynamic, Scope.Global, null, null, parent, null, null, null, null); + } + public ConfigKey(Class type, String name, String category, String defaultValue, String description, boolean isDynamic, Scope scope, T multiplier) { + this(type, name, category, defaultValue, description, isDynamic, scope, multiplier, null, null, null, null, null, null); + } + + public ConfigKey(Class type, String name, String category, String defaultValue, String description, boolean isDynamic, Scope scope, T multiplier, String parent) { + this(type, name, category, defaultValue, description, isDynamic, scope, multiplier, null, parent, null, null, null, null); + } + + public ConfigKey(Class type, String name, String category, String defaultValue, String description, boolean isDynamic, Scope scope, T multiplier, + String displayText, String parent, Ternary group, Pair subGroup) { + this(type, name, category, defaultValue, description, isDynamic, scope, multiplier, null, parent, null, null, null, null); + } + + public ConfigKey(Class type, String name, String category, String defaultValue, String description, boolean isDynamic, Scope scope, T multiplier, + String displayText, String parent, Ternary group, Pair subGroup, Kind kind, String options) { _category = category; _type = type; _name = name; _defaultValue = defaultValue; _description = description; + _displayText = displayText; _scope = scope; _isDynamic = isDynamic; _multiplier = multiplier; + _parent = parent; + _group = group; + _subGroup = subGroup; + _kind = kind; + _options = options; } @Deprecated @@ -111,6 +175,10 @@ public class ConfigKey { this(type, name, category, defaultValue, description, isDynamic, Scope.Global, null); } + public ConfigKey(Class type, String name, String category, String defaultValue, String description, boolean isDynamic, String parent) { + this(type, name, category, defaultValue, description, isDynamic, Scope.Global, null, null, parent, null, null); + } + public T multiplier() { return _multiplier; } diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationGroupDao.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationGroupDao.java new file mode 100644 index 00000000000..1aa1d9c8169 --- /dev/null +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationGroupDao.java @@ -0,0 +1,25 @@ +// 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.framework.config.dao; + +import org.apache.cloudstack.framework.config.impl.ConfigurationGroupVO; + +import com.cloud.utils.db.GenericDao; + +public interface ConfigurationGroupDao extends GenericDao { + ConfigurationGroupVO findByName(String name); +} diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationGroupDaoImpl.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationGroupDaoImpl.java new file mode 100644 index 00000000000..d0fafcea1d9 --- /dev/null +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationGroupDaoImpl.java @@ -0,0 +1,44 @@ +// 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.framework.config.dao; + +import org.apache.cloudstack.framework.config.impl.ConfigurationGroupVO; +import org.springframework.stereotype.Component; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Component +public class ConfigurationGroupDaoImpl extends GenericDaoBase implements ConfigurationGroupDao { + + final SearchBuilder nameSearch; + + public ConfigurationGroupDaoImpl() { + super(); + + nameSearch = createSearchBuilder(); + nameSearch.and("name", nameSearch.entity().getName(), SearchCriteria.Op.EQ); + } + + @Override + public ConfigurationGroupVO findByName(String name) { + SearchCriteria sc = nameSearch.create(); + sc.setParameters("name", name); + return findOneIncludingRemovedBy(sc); + } +} diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationSubGroupDao.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationSubGroupDao.java new file mode 100644 index 00000000000..10cd86b5b1a --- /dev/null +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationSubGroupDao.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.framework.config.dao; + +import java.util.List; + +import org.apache.cloudstack.framework.config.impl.ConfigurationSubGroupVO; + +import com.cloud.utils.db.GenericDao; + +public interface ConfigurationSubGroupDao extends GenericDao { + ConfigurationSubGroupVO findByName(String name); + ConfigurationSubGroupVO startsWithName(String name); + ConfigurationSubGroupVO findByKeyword(String keyword); + ConfigurationSubGroupVO findByNameAndGroup(String name, Long groupId); + List findByGroup(Long groupId); +} diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationSubGroupDaoImpl.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationSubGroupDaoImpl.java new file mode 100644 index 00000000000..718adeec542 --- /dev/null +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationSubGroupDaoImpl.java @@ -0,0 +1,131 @@ +// 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.framework.config.dao; + +import java.util.Arrays; +import java.util.List; +import java.util.function.BiPredicate; + +import org.apache.cloudstack.framework.config.impl.ConfigurationSubGroupVO; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Component +public class ConfigurationSubGroupDaoImpl extends GenericDaoBase implements ConfigurationSubGroupDao { + + final SearchBuilder nameSearch; + final SearchBuilder groupSearch; + final SearchBuilder nameAndGroupSearch; + final SearchBuilder keywordSearch; + + public ConfigurationSubGroupDaoImpl() { + super(); + + nameSearch = createSearchBuilder(); + nameSearch.and("name", nameSearch.entity().getName(), SearchCriteria.Op.LIKE); + nameSearch.done(); + + groupSearch = createSearchBuilder(); + groupSearch.and("groupId", groupSearch.entity().getGroupId(), SearchCriteria.Op.EQ); + groupSearch.done(); + + nameAndGroupSearch = createSearchBuilder(); + nameAndGroupSearch.and("name", nameAndGroupSearch.entity().getName(), SearchCriteria.Op.EQ); + nameAndGroupSearch.and("groupId", nameAndGroupSearch.entity().getGroupId(), SearchCriteria.Op.EQ); + nameAndGroupSearch.done(); + + keywordSearch = createSearchBuilder(); + keywordSearch.and("keywords", keywordSearch.entity().getKeywords(), SearchCriteria.Op.NNULL); + keywordSearch.done(); + } + + @Override + public ConfigurationSubGroupVO findByName(String name) { + SearchCriteria sc = nameSearch.create(); + sc.setParameters("name", name); + return findOneIncludingRemovedBy(sc); + } + + @Override + public ConfigurationSubGroupVO startsWithName(String name) { + SearchCriteria sc = nameSearch.create(); + sc.setParameters("name", name + "%"); + return findOneIncludingRemovedBy(sc); + } + + private ConfigurationSubGroupVO matchKeywordBy(BiPredicate matcher, List configurationSubGroups, String keyword) { + for (ConfigurationSubGroupVO configurationSubGroup : configurationSubGroups) { + if (StringUtils.isBlank(configurationSubGroup.getKeywords())) { + continue; + } + + String[] configKeywords = configurationSubGroup.getKeywords().split(","); + if (configKeywords.length <= 0) { + continue; + } + + List keywords = Arrays.asList(configKeywords); + for (String configKeyword : keywords) { + if (StringUtils.isNotBlank(configKeyword)) { + configKeyword = configKeyword.strip().toLowerCase(); + if (matcher.test(keyword, configKeyword)) { + return configurationSubGroup; + } + } + } + } + return null; + } + + @Override + public ConfigurationSubGroupVO findByKeyword(String keyword) { + if (StringUtils.isBlank(keyword)) { + return null; + } + + SearchCriteria sc = keywordSearch.create(); + List configurationSubGroups = listBy(sc); + BiPredicate equals = (a, b) -> { return a.equalsIgnoreCase(b); }; + ConfigurationSubGroupVO configSubGroup = matchKeywordBy(equals, configurationSubGroups, keyword); + if (configSubGroup == null) { + BiPredicate startsWith = (a, b) -> { return a.startsWith(b); }; + configSubGroup = matchKeywordBy(startsWith, configurationSubGroups, keyword.toLowerCase()); + } + return configSubGroup; + } + + @Override + public ConfigurationSubGroupVO findByNameAndGroup(String name, Long groupId) { + SearchCriteria sc = nameAndGroupSearch.create(); + sc.setParameters("name", name); + sc.setParameters("groupId", groupId); + return findOneIncludingRemovedBy(sc); + } + + @Override + public List findByGroup(Long groupId) { + SearchCriteria sc = groupSearch.create(); + sc.setParameters("groupId", groupId); + final Filter filter = new Filter(ConfigurationSubGroupVO.class, "precedence", true, null, null); + return listIncludingRemovedBy(sc, filter); + } +} diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java index 1cb93dc542a..75a3ea4d947 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java @@ -33,10 +33,14 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.ScopedConfigStorage; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao; import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; import com.cloud.utils.exception.CloudRuntimeException; /** @@ -70,6 +74,10 @@ public class ConfigDepotImpl implements ConfigDepot, ConfigDepotAdmin { private final static Logger s_logger = Logger.getLogger(ConfigDepotImpl.class); @Inject ConfigurationDao _configDao; + @Inject + ConfigurationGroupDao _configGroupDao; + @Inject + ConfigurationSubGroupDao _configSubGroupDao; List _configurables; List _scopedStorages; Set _configured = Collections.synchronizedSet(new HashSet()); @@ -139,6 +147,28 @@ public class ConfigDepotImpl implements ConfigDepot, ConfigDepotAdmin { } private void createOrupdateConfigObject(Date date, String componentName, ConfigKey key, String value) { + Long groupId = 1L; + Long subGroupId = 1L; + if (key.group() != null) { + Ternary group = key.group(); + ConfigurationGroupVO groupVO = _configGroupDao.findByName(group.first()); + if (groupVO == null) { + groupVO = new ConfigurationGroupVO(group.first(), group.second(), group.third()); + groupVO = _configGroupDao.persist(groupVO); + } + groupId = groupVO.getId(); + } + + if (key.subGroup() != null) { + Pair subGroup = key.subGroup(); + ConfigurationSubGroupVO subGroupVO = _configSubGroupDao.findByNameAndGroup(subGroup.first(), groupId); + if (subGroupVO == null) { + subGroupVO = new ConfigurationSubGroupVO(); + subGroupVO = _configSubGroupDao.persist(subGroupVO); + } + subGroupId = subGroupVO.getId(); + } + ConfigurationVO vo = _configDao.findById(key.key()); if (vo == null) { vo = new ConfigurationVO(componentName, key); @@ -146,8 +176,25 @@ public class ConfigDepotImpl implements ConfigDepot, ConfigDepotAdmin { if (value != null) { vo.setValue(value); } + + if (key.group() == null && key.subGroup() == null ) { + Pair configGroupAndSubGroup = getConfigurationGroupAndSubGroupByName(key.key()); + vo.setGroupId(configGroupAndSubGroup.first()); + vo.setSubGroupId(configGroupAndSubGroup.second()); + } else { + vo.setGroupId(groupId); + vo.setSubGroupId(subGroupId); + } + if (key.kind() != null) { + vo.setKind(key.kind().toString()); + } + if (key.options() != null) { + vo.setOptions(key.options()); + } + _configDao.persist(vo); } else { + boolean configUpdated = false; if (vo.isDynamic() != key.isDynamic() || !ObjectUtils.equals(vo.getDescription(), key.description()) || !ObjectUtils.equals(vo.getDefaultValue(), key.defaultValue()) || !ObjectUtils.equals(vo.getScope(), key.scope().toString()) || !ObjectUtils.equals(vo.getComponent(), componentName)) { @@ -157,6 +204,48 @@ public class ConfigDepotImpl implements ConfigDepot, ConfigDepotAdmin { vo.setScope(key.scope().toString()); vo.setComponent(componentName); vo.setUpdated(date); + configUpdated = true; + } + + if (key.displayText() != null && !ObjectUtils.equals(vo.getDisplayText(), key.displayText())) { + vo.setDisplayText(key.displayText()); + configUpdated = true; + } + + if (key.parent() != null && !ObjectUtils.equals(vo.getParent(), key.parent())) { + vo.setParent(key.parent()); + configUpdated = true; + } + + if (key.group() == null && key.subGroup() == null ) { + Pair configGroupAndSubGroup = getConfigurationGroupAndSubGroupByName(key.key()); + if (configGroupAndSubGroup.first() != 1 && configGroupAndSubGroup.second() != 1) { + vo.setGroupId(configGroupAndSubGroup.first()); + vo.setSubGroupId(configGroupAndSubGroup.second()); + configUpdated = true; + } + } + + if (key.group() != null && !ObjectUtils.equals(vo.getGroupId(), groupId)) { + vo.setGroupId(groupId); + configUpdated = true; + } + + if (key.subGroup() != null && !ObjectUtils.equals(vo.getSubGroupId(), subGroupId)) { + vo.setSubGroupId(subGroupId); + configUpdated = true; + } + + if (key.kind() != null) { + vo.setKind(key.kind().toString()); + configUpdated = true; + } + if (key.options() != null) { + vo.setOptions(key.options()); + configUpdated = true; + } + + if (configUpdated) { _configDao.persist(vo); } } @@ -227,6 +316,31 @@ public class ConfigDepotImpl implements ConfigDepot, ConfigDepotAdmin { @Override public void createOrUpdateConfigObject(String componentName, ConfigKey key, String value) { createOrupdateConfigObject(new Date(), componentName, key, value); + } + @Override + public Pair getConfigurationGroupAndSubGroupByName(String configName) { + Long subGroupId = 1L; + Long groupId = 1L; + if (StringUtils.isNotBlank(configName)) { + String[] nameWords = configName.split("\\."); + if (nameWords.length > 0) { + for (int index = 0; index < nameWords.length; index++) { + ConfigurationSubGroupVO configSubGroup = _configSubGroupDao.findByName(nameWords[index]); + + if (configSubGroup == null) { + configSubGroup = _configSubGroupDao.findByKeyword(nameWords[index]); + } + + if (configSubGroup != null) { + subGroupId = configSubGroup.getId(); + groupId = configSubGroup.getGroupId(); + break; + } + } + } + } + + return new Pair<>(groupId, subGroupId); } } diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationGroupVO.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationGroupVO.java new file mode 100644 index 00000000000..5c232b1b015 --- /dev/null +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationGroupVO.java @@ -0,0 +1,91 @@ +// 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.framework.config.impl; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.config.ConfigurationGroup; + +@Entity +@Table(name = "configuration_group") +public class ConfigurationGroupVO implements ConfigurationGroup { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id = 1; + + @Column(name = "name") + private String name; + + @Column(name = "description", length = 1024) + private String description = null; + + @Column(name = "precedence") + private Long precedence = 999L; + + protected ConfigurationGroupVO() { + } + + public ConfigurationGroupVO(String name, String description) { + this.name = name; + this.description = description; + this.precedence = 999L; + } + + public ConfigurationGroupVO(String name, String description, Long precedence) { + this.name = name; + this.description = description; + this.precedence = precedence; + } + + @Override + public long getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public Long getPrecedence() { + return precedence; + } + + public void setPrecedence(Long precedence) { + this.precedence = precedence; + } +} diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationSubGroupVO.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationSubGroupVO.java new file mode 100644 index 00000000000..cb529e1cca0 --- /dev/null +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationSubGroupVO.java @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.framework.config.impl; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.config.ConfigurationSubGroup; + +@Entity +@Table(name = "configuration_subgroup") +public class ConfigurationSubGroupVO implements ConfigurationSubGroup { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id = 1; + + @Column(name = "name") + private String name; + + @Column(name = "keywords") + private String keywords = null; + + @Column(name = "precedence") + private Long precedence = 999L; + + @Column(name = "group_id") + private Long groupId; + + protected ConfigurationSubGroupVO() { + } + + public ConfigurationSubGroupVO(String name, String keywords) { + this.name = name; + this.keywords = keywords; + this.precedence = 999L; + } + + public ConfigurationSubGroupVO(String name, String keywords, Long precedence) { + this.name = name; + this.keywords = keywords; + this.precedence = precedence; + } + + @Override + public long getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getKeywords() { + return keywords; + } + + public void setKeywords(String keywords) { + this.keywords = keywords; + } + + @Override + public Long getPrecedence() { + return precedence; + } + + public void setPrecedence(Long precedence) { + this.precedence = precedence; + } + + @Override + public Long getGroupId() { + return groupId; + } + + public void setGroupId(Long groupId) { + this.groupId = groupId; + } +} diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java index 7cd9afb8384..08ec9bfe83f 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java @@ -27,6 +27,7 @@ import javax.persistence.TemporalType; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.commons.lang3.StringUtils; import com.cloud.utils.crypt.DBEncryptionUtil; @@ -65,20 +66,50 @@ public class ConfigurationVO implements Configuration { @Temporal(value = TemporalType.TIMESTAMP) private Date updated; + @Column(name = "group_id") + private Long groupId = 1L; + + @Column(name = "subgroup_id") + private Long subGroupId = 1L; + + @Column(name = "parent") + private String parent; + + @Column(name = "display_text", length = 255) + private String displayText; + + @Column(name = "kind") + private String kind; + + @Column(name = "options") + private String options; + protected ConfigurationVO() { } public ConfigurationVO(String category, String instance, String component, String name, String value, String description) { + this(category, instance, component, name, value, description, null, null); + } + + public ConfigurationVO(String category, String instance, String component, String name, String value, String description, String displayText, String parentConfigName) { + this(category, instance, component, name, value, description, displayText, parentConfigName, null, null); + } + + public ConfigurationVO(String category, String instance, String component, String name, String value, String description, String displayText, String parentConfigName, Long groupId, Long subGroupId) { this.category = category; this.instance = instance; this.component = component; this.name = name; this.description = description; + this.parent = parentConfigName; setValue(value); + setDisplayText(displayText); + setGroupId(groupId); + setSubGroupId(subGroupId); } public ConfigurationVO(String component, ConfigKey key) { - this(key.category(), "DEFAULT", component, key.key(), key.defaultValue(), key.description()); + this(key.category(), "DEFAULT", component, key.key(), key.defaultValue(), key.description(), key.displayText(), key.parent()); defaultValue = key.defaultValue(); dynamic = key.isDynamic(); scope = key.scope() != null ? key.scope().toString() : null; @@ -186,4 +217,83 @@ public class ConfigurationVO implements Configuration { public void setUpdated(Date updated) { this.updated = updated; } + + @Override + public Long getGroupId() { + return groupId; + } + + public void setGroupId(Long groupId) { + if (groupId != null) { + this.groupId = groupId; + } else { + this.groupId = 1L; + } + } + + @Override + public Long getSubGroupId() { + return subGroupId; + } + + public void setSubGroupId(Long subGroupId) { + if (subGroupId != null) { + this.subGroupId = subGroupId; + } else { + this.subGroupId = 1L; + } + } + + @Override + public String getParent() { + return parent; + } + + public void setParent(String parent) { + this.parent = parent; + } + + @Override + public String getDisplayText() { + if (StringUtils.isNotBlank(displayText)) { + return displayText; + } + + String displayText = ""; + String name = getName(); + if (StringUtils.isNotBlank(name)) { + name = name.replace(".", " "); + displayText = name.substring(0, 1).toUpperCase() + name.substring(1); + } + return displayText; + } + + public void setDisplayText(String displayText) { + if (StringUtils.isBlank(displayText)) { + String name = getName(); + if (StringUtils.isNotBlank(name)) { + name = name.replace(".", " "); + displayText = name.substring(0, 1).toUpperCase() + name.substring(1); + } + } + + this.displayText = displayText; + } + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + + public String getOptions() { + return options; + } + + public void setOptions(String options) { + this.options = options; + } + } diff --git a/framework/config/src/main/resources/META-INF/cloudstack/system/spring-framework-config-system-context.xml b/framework/config/src/main/resources/META-INF/cloudstack/system/spring-framework-config-system-context.xml index be116fc5e77..1a3bcf441e4 100644 --- a/framework/config/src/main/resources/META-INF/cloudstack/system/spring-framework-config-system-context.xml +++ b/framework/config/src/main/resources/META-INF/cloudstack/system/spring-framework-config-system-context.xml @@ -45,6 +45,12 @@ + + + + diff --git a/framework/config/src/test/java/org/apache/cloudstack/framework/config/ConfigKeyTest.java b/framework/config/src/test/java/org/apache/cloudstack/framework/config/ConfigKeyTest.java index 01a3509d7f0..a3a8aadfa60 100644 --- a/framework/config/src/test/java/org/apache/cloudstack/framework/config/ConfigKeyTest.java +++ b/framework/config/src/test/java/org/apache/cloudstack/framework/config/ConfigKeyTest.java @@ -39,12 +39,12 @@ public class ConfigKeyTest { @Test public void testIsSameKeyAs() { ConfigKey key = new ConfigKey("cat", String.class, "naam", "nick", "bijnaam", true, Scope.Cluster); - Assert.assertTrue("1 and one should be considdered the same address", key.isSameKeyAs("naam")); + Assert.assertTrue("1 and one should be considered the same address", key.isSameKeyAs("naam")); } @Test(expected = CloudRuntimeException.class) public void testIsSameKeyAsThrowingCloudRuntimeException() { ConfigKey key = new ConfigKey("hond", Boolean.class, "naam", "truus", "thrown name", false); - Assert.assertFalse("zero and 0L should be considdered the same address", key.isSameKeyAs(0L)); + Assert.assertFalse("zero and 0L should be considered the same address", key.isSameKeyAs(0L)); } } diff --git a/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotAdminTest.java b/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotAdminTest.java index da76804eb77..46490335bf5 100644 --- a/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotAdminTest.java +++ b/framework/config/src/test/java/org/apache/cloudstack/framework/config/impl/ConfigDepotAdminTest.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.framework.config.impl; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Matchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -25,9 +26,13 @@ import java.util.ArrayList; import junit.framework.TestCase; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.apache.cloudstack.framework.config.ConfigDepot; @@ -36,11 +41,15 @@ import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.ScopedConfigStorage; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; import com.cloud.utils.db.EntityManager; public class ConfigDepotAdminTest extends TestCase { private final static ConfigKey DynamicIntCK = new ConfigKey(Integer.class, "dynIntKey", "Advance", "10", "Test Key", true); private final static ConfigKey StaticIntCK = new ConfigKey(Integer.class, "statIntKey", "Advance", "10", "Test Key", false); + private final static ConfigKey TestCK = new ConfigKey<>(Integer.class, "testKey", "Advance", "30", "Test Key", false, + ConfigKey.Scope.Global, null, "Test Display Text", null, new Ternary<>("TestGroup", "Test Group", 3L), new Pair<>("Test SubGroup", 1L)); @Mock Configurable _configurable; @@ -56,6 +65,12 @@ public class ConfigDepotAdminTest extends TestCase { @Mock ConfigurationDao _configDao; + @Mock + ConfigurationGroupDao _configGroupDao; + + @Mock + ConfigurationSubGroupDao _configSubGroupDao; + @Mock ScopedConfigStorage _scopedStorage; @@ -68,6 +83,8 @@ public class ConfigDepotAdminTest extends TestCase { MockitoAnnotations.initMocks(this); _depotAdmin = new ConfigDepotImpl(); _depotAdmin._configDao = _configDao; + _depotAdmin._configGroupDao = _configGroupDao; + _depotAdmin._configSubGroupDao = _configSubGroupDao; _depotAdmin._configurables = new ArrayList(); _depotAdmin._configurables.add(_configurable); _depotAdmin._scopedStorages = new ArrayList(); @@ -80,22 +97,58 @@ public class ConfigDepotAdminTest extends TestCase { dynamicIntCV.setValue("100"); ConfigurationVO staticIntCV = new ConfigurationVO("UnitTestComponent", StaticIntCK); dynamicIntCV.setValue("200"); + ConfigurationVO testCV = new ConfigurationVO("UnitTestComponent", TestCK); when(_configurable.getConfigComponentName()).thenReturn("UnitTestComponent"); - when(_configurable.getConfigKeys()).thenReturn(new ConfigKey[] {DynamicIntCK, StaticIntCK}); + when(_configurable.getConfigKeys()).thenReturn(new ConfigKey[] {DynamicIntCK, StaticIntCK, TestCK}); when(_configDao.findById(StaticIntCK.key())).thenReturn(null); when(_configDao.findById(DynamicIntCK.key())).thenReturn(dynamicIntCV); + when(_configDao.findById(TestCK.key())).thenReturn(testCV); when(_configDao.persist(any(ConfigurationVO.class))).thenReturn(dynamicIntCV); - _depotAdmin.populateConfigurations(); // This is once because DynamicIntCK is returned. verify(_configDao, times(1)).persist(any(ConfigurationVO.class)); when(_configDao.findById(DynamicIntCK.key())).thenReturn(dynamicIntCV); + when(_configDao.findById(TestCK.key())).thenReturn(null); _depotAdmin._configured.clear(); _depotAdmin.populateConfigurations(); - // This is two because DynamicIntCK also returns null. - verify(_configDao, times(2)).persist(any(ConfigurationVO.class)); + // This is three because DynamicIntCK, TestCK also returns null. + verify(_configDao, times(3)).persist(any(ConfigurationVO.class)); + } + + @Test + public void testDefaultConfigurationGroupAndSubGroup() { + Mockito.when(_configSubGroupDao.findByName(anyString())).thenReturn(null); + Mockito.when(_configSubGroupDao.findByKeyword(anyString())).thenReturn(null); + + Pair configGroupAndSubGroup = _depotAdmin.getConfigurationGroupAndSubGroupByName("test.storage.config.setting"); + + Assert.assertEquals(1L, configGroupAndSubGroup.first().longValue()); + Assert.assertEquals(1L, configGroupAndSubGroup.second().longValue()); + } + + @Test + public void testConfigurationGroupAndSubGroup() { + ConfigurationGroupVO testGroup = new ConfigurationGroupVO("TestGroup", "Test Group", 3L); + ConfigurationSubGroupVO testSubGroup = new ConfigurationSubGroupVO("TestSubGroup", null, 1L); + testSubGroup.setGroupId(9L); + Mockito.when(_configSubGroupDao.findByName("storage")).thenReturn(testSubGroup); + Mockito.when(_configSubGroupDao.findByKeyword(anyString())).thenReturn(null); + + Pair configGroupAndSubGroup = _depotAdmin.getConfigurationGroupAndSubGroupByName("test.storage.config.setting"); + + Assert.assertEquals(9L, configGroupAndSubGroup.first().longValue()); + Assert.assertEquals(1L, configGroupAndSubGroup.second().longValue()); + + testSubGroup.setGroupId(5L); + Mockito.when(_configSubGroupDao.findByName(anyString())).thenReturn(null); + Mockito.when(_configSubGroupDao.findByKeyword("storage")).thenReturn(testSubGroup); + + configGroupAndSubGroup = _depotAdmin.getConfigurationGroupAndSubGroupByName("test.storage.config.setting"); + + Assert.assertEquals(5L, configGroupAndSubGroup.first().longValue()); + Assert.assertEquals(1L, configGroupAndSubGroup.second().longValue()); } } diff --git a/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobManagerTestConfiguration.java b/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobManagerTestConfiguration.java index a70913cd983..91c2cc3a760 100644 --- a/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobManagerTestConfiguration.java +++ b/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobManagerTestConfiguration.java @@ -18,14 +18,17 @@ */ package org.apache.cloudstack.framework.jobs; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - import org.apache.cloudstack.framework.config.ConfigDepot; import org.apache.cloudstack.framework.config.ScopedConfigStorage; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl; import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import com.cloud.storage.dao.StoragePoolDetailsDaoImpl; @@ -42,6 +45,16 @@ public class AsyncJobManagerTestConfiguration { return new ConfigurationDaoImpl(); } + @Bean + public ConfigurationGroupDao configGroupDao() { + return new ConfigurationGroupDaoImpl(); + } + + @Bean + public ConfigurationSubGroupDao configSubGroupDao() { + return new ConfigurationSubGroupDaoImpl(); + } + @Bean public ScopedConfigStorage scopedConfigStorage() { return new StoragePoolDetailsDaoImpl(); diff --git a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java index eda9a231fe5..a077bc8f4f0 100644 --- a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java +++ b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java @@ -136,14 +136,14 @@ public class ExtensionRegistry implements Registry, Configurable, BeanNa List> result = new ArrayList>(); if (orderConfigKey != null && orderConfigKeyObj == null) { - orderConfigKeyObj = new ConfigKey("Advanced", String.class, orderConfigKey, orderConfigDefault, "The order of precedence for the extensions", false); + orderConfigKeyObj = new ConfigKey<>(String.class, orderConfigKey, "Advanced", orderConfigDefault, "The order of precedence for the extensions", false, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Order, orderConfigDefault); } if (orderConfigKeyObj != null) result.add(orderConfigKeyObj); if (excludeKey != null && excludeKeyObj == null) { - excludeKeyObj = new ConfigKey("Advanced", String.class, excludeKey, excludeDefault, "Extensions to exclude from being registered", false); + excludeKeyObj = new ConfigKey<>(String.class, excludeKey, "Advanced", excludeDefault, "Extensions to exclude from being registered", false, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null); } if (excludeKeyObj != null) { diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java index 138889a2fb3..420f3552720 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java @@ -45,33 +45,39 @@ public interface KubernetesClusterService extends PluggableService, Configurable "cloud.kubernetes.cluster.network.offering", "DefaultNetworkOfferingforKubernetesService", "Name of the network offering that will be used to create isolated network in which Kubernetes cluster VMs will be launched", - false); + false, + KubernetesServiceEnabled.key()); static final ConfigKey KubernetesClusterStartTimeout = new ConfigKey("Advanced", Long.class, "cloud.kubernetes.cluster.start.timeout", "3600", "Timeout interval (in seconds) in which start operation for a Kubernetes cluster should be completed", - true); + true, + KubernetesServiceEnabled.key()); static final ConfigKey KubernetesClusterScaleTimeout = new ConfigKey("Advanced", Long.class, "cloud.kubernetes.cluster.scale.timeout", "3600", "Timeout interval (in seconds) in which scale operation for a Kubernetes cluster should be completed", - true); + true, + KubernetesServiceEnabled.key()); static final ConfigKey KubernetesClusterUpgradeTimeout = new ConfigKey("Advanced", Long.class, "cloud.kubernetes.cluster.upgrade.timeout", "3600", "Timeout interval (in seconds) in which upgrade operation for a Kubernetes cluster should be completed. Not strictly obeyed while upgrade is in progress on a node", - true); + true, + KubernetesServiceEnabled.key()); static final ConfigKey KubernetesClusterExperimentalFeaturesEnabled = new ConfigKey("Advanced", Boolean.class, "cloud.kubernetes.cluster.experimental.features.enabled", "false", "Indicates whether experimental feature for Kubernetes cluster such as Docker private registry are enabled or not", - true); + true, + KubernetesServiceEnabled.key()); static final ConfigKey KubernetesMaxClusterSize = new ConfigKey("Advanced", Integer.class, "cloud.kubernetes.cluster.max.size", "10", "Maximum size of the kubernetes cluster.", - true, ConfigKey.Scope.Account); - + true, + ConfigKey.Scope.Account, + KubernetesServiceEnabled.key()); KubernetesCluster findById(final Long id); diff --git a/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterServer.java b/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterServer.java index 0ec83066f61..f0f5e3c6987 100644 --- a/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterServer.java +++ b/plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterServer.java @@ -26,10 +26,10 @@ public interface PrometheusExporterServer extends Manager { "Enable the prometheus exporter plugin, management server restart needed.", false); ConfigKey PrometheusExporterServerPort = new ConfigKey<>("Advanced", Integer.class, "prometheus.exporter.port", "9595", - "The prometheus exporter server port", true); + "The prometheus exporter server port", true, EnablePrometheusExporter.key()); ConfigKey PrometheusExporterAllowedAddresses = new ConfigKey<>("Advanced", String.class, "prometheus.exporter.allowed.ips", "127.0.0.1", - "List of comma separated prometheus server ips (with no spaces) that should be allowed to access the URLs", true); + "List of comma separated prometheus server ips (with no spaces) that should be allowed to access the URLs", true, EnablePrometheusExporter.key()); ConfigKey PrometheusExporterOfferingCountLimit = new ConfigKey<>("Advanced", Integer.class, "prometheus.exporter.offering.output.limit", "-1", "Limit the number of output for cloudstack_vms_total_by_size to the provided value. -1 for unlimited output.", true); diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java index 989d74b6c95..d80d812916c 100644 --- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java +++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/IntegrationTestConfiguration.java @@ -21,6 +21,8 @@ import java.io.IOException; import javax.inject.Inject; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl; import org.eclipse.jetty.security.IdentityService; import org.mockito.Matchers; import org.mockito.Mockito; @@ -304,14 +306,14 @@ import com.cloud.vm.snapshot.dao.VMSnapshotDaoImpl; ApiDBUtils.class, ApplicationLoadBalancerRuleDaoImpl.class, AsyncJobDaoImpl.class, AsyncJobJoinDaoImpl.class, AsyncJobJoinMapDaoImpl.class, AsyncJobJournalDaoImpl.class, AsyncJobManagerImpl.class, AutoScalePolicyConditionMapDaoImpl.class, AutoScalePolicyDaoImpl.class, AutoScaleVmGroupDaoImpl.class, AutoScaleVmGroupPolicyMapDaoImpl.class, AutoScaleVmProfileDaoImpl.class, CapacityDaoImpl.class, ClusterDaoImpl.class, ClusterDetailsDaoImpl.class, - ConditionDaoImpl.class, ConfigurationDaoImpl.class, ConfigurationManagerImpl.class, ConfigurationServerImpl.class, ConsoleProxyDaoImpl.class, - ContrailElementImpl.class, ContrailGuru.class, ContrailManagerImpl.class, CounterDaoImpl.class, DataCenterDaoImpl.class, DataCenterDetailsDaoImpl.class, DataCenterIpAddressDaoImpl.class, - DataCenterJoinDaoImpl.class, DataCenterLinkLocalIpAddressDaoImpl.class, DataCenterVnetDaoImpl.class, DcDetailsDaoImpl.class, DedicatedResourceDaoImpl.class, + ConditionDaoImpl.class, ConfigurationDaoImpl.class, ConfigurationGroupDaoImpl.class, ConfigurationSubGroupDaoImpl.class, ConfigurationManagerImpl.class, ConfigurationServerImpl.class, + ConsoleProxyDaoImpl.class, ContrailElementImpl.class, ContrailGuru.class, ContrailManagerImpl.class, CounterDaoImpl.class, DataCenterDaoImpl.class, DataCenterDetailsDaoImpl.class, + DataCenterIpAddressDaoImpl.class, DataCenterJoinDaoImpl.class, DataCenterLinkLocalIpAddressDaoImpl.class, DataCenterVnetDaoImpl.class, DcDetailsDaoImpl.class, DedicatedResourceDaoImpl.class, DiskOfferingDaoImpl.class, DiskOfferingJoinDaoImpl.class, DomainDaoImpl.class, DomainDetailsDaoImpl.class, DomainManagerImpl.class, DomainRouterDaoImpl.class, DomainRouterJoinDaoImpl.class, EventDaoImpl.class, EventJoinDaoImpl.class, EventUtils.class, ExtensionRegistry.class, FirewallManagerImpl.class, FirewallRulesCidrsDaoImpl.class, FirewallRulesDaoImpl.class, GuestOSCategoryDaoImpl.class, GuestOSDaoImpl.class, HostDaoImpl.class, HostDetailsDaoImpl.class, HostJoinDaoImpl.class, HostPodDaoImpl.class, HostTagsDaoImpl.class, HostTransferMapDaoImpl.class, HypervisorCapabilitiesDaoImpl.class, HypervisorGuruManagerImpl.class, - ImageStoreDaoImpl.class, ImageStoreJoinDaoImpl.class, InstanceGroupDaoImpl.class, InstanceGroupJoinDaoImpl.class, + ImageStoreDaoImpl.class, ImageStoreJoinDaoImpl.class, InstanceGroupDaoImpl.class, InstanceGroupJoinDaoImpl.class, InstanceGroupVMMapDaoImpl.class, InternalLoadBalancerElement.class, IPAddressDaoImpl.class, IpAddressManagerImpl.class, Ipv6AddressManagerImpl.class, ItWorkDaoImpl.class, LBHealthCheckPolicyDaoImpl.class, LBStickinessPolicyDaoImpl.class, LaunchPermissionDaoImpl.class, LoadBalancerDaoImpl.class, LoadBalancerVMMapDaoImpl.class, LoadBalancingRulesManagerImpl.class, ManagementServerHostDaoImpl.class, MockAccountManager.class, NetworkACLDaoImpl.class, NetworkACLItemDaoImpl.class, NetworkACLManagerImpl.class, diff --git a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManager.java b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManager.java index c65f8b3ad1f..e52a7e32695 100644 --- a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManager.java +++ b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManager.java @@ -61,8 +61,8 @@ public interface SAML2AuthManager extends PluggableAPIAuthenticator, PluggableSe public static final ConfigKey SAMLDefaultIdentityProviderId = new ConfigKey("Advanced", String.class, "saml2.default.idpid", "https://openidp.feide.no", "The default IdP entity ID to use only in case of multiple IdPs", true); - public static final ConfigKey SAMLSignatureAlgorithm = new ConfigKey("Advanced", String.class, "saml2.sigalg", "SHA1", - "The algorithm to use to when signing a SAML request. Default is SHA1, allowed algorithms: SHA1, SHA256, SHA384, SHA512", true); + public static final ConfigKey SAMLSignatureAlgorithm = new ConfigKey<>(String.class, "saml2.sigalg", "Advanced", "SHA1", + "The algorithm to use to when signing a SAML request. Default is SHA1, allowed algorithms: SHA1, SHA256, SHA384, SHA512", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, "SHA1,SHA256,SHA384,SHA512"); public static final ConfigKey SAMLAppendDomainSuffix = new ConfigKey("Advanced", Boolean.class, "saml2.append.idpdomain", "false", "If enabled, create account/user dialog with SAML SSO enabled will append the IdP domain to the user or account name in the UI dialog", true); diff --git a/server/conf/migration-components.xml b/server/conf/migration-components.xml index 6b107dfc31b..168b72abf54 100644 --- a/server/conf/migration-components.xml +++ b/server/conf/migration-components.xml @@ -22,6 +22,8 @@ under the License. + + diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index e12e612fe51..0b1dc67280b 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -65,7 +65,9 @@ import org.apache.cloudstack.api.response.CapabilityResponse; import org.apache.cloudstack.api.response.CapacityResponse; import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.ConditionResponse; +import org.apache.cloudstack.api.response.ConfigurationGroupResponse; import org.apache.cloudstack.api.response.ConfigurationResponse; +import org.apache.cloudstack.api.response.ConfigurationSubGroupResponse; import org.apache.cloudstack.api.response.ControlledEntityResponse; import org.apache.cloudstack.api.response.ControlledViewEntityResponse; import org.apache.cloudstack.api.response.CounterResponse; @@ -170,6 +172,8 @@ import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.backup.dao.BackupOfferingDao; import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.config.ConfigurationGroup; +import org.apache.cloudstack.config.ConfigurationSubGroup; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.direct.download.DirectDownloadCertificate; import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap; @@ -560,6 +564,9 @@ public class ApiResponseHelper implements ResponseGenerator { public ConfigurationResponse createConfigurationResponse(Configuration cfg) { ConfigurationResponse cfgResponse = new ConfigurationResponse(); cfgResponse.setCategory(cfg.getCategory()); + Pair configGroupAndSubGroup = _configMgr.getConfigurationGroupAndSubGroup(cfg.getName()); + cfgResponse.setGroup(configGroupAndSubGroup.first()); + cfgResponse.setSubGroup(configGroupAndSubGroup.second()); cfgResponse.setDescription(cfg.getDescription()); cfgResponse.setName(cfg.getName()); if (cfg.isEncrypted()) { @@ -567,12 +574,48 @@ public class ApiResponseHelper implements ResponseGenerator { } else { cfgResponse.setValue(cfg.getValue()); } + cfgResponse.setDefaultValue(cfg.getDefaultValue()); cfgResponse.setIsDynamic(cfg.isDynamic()); + cfgResponse.setComponent(cfg.getComponent()); + if (cfg.getParent() != null) { + cfgResponse.setParent(cfg.getParent()); + } + cfgResponse.setDisplayText(cfg.getDisplayText()); + cfgResponse.setType(_configMgr.getConfigurationType(cfg.getName())); + if (cfg.getOptions() != null) { + cfgResponse.setOptions(cfg.getOptions()); + } cfgResponse.setObjectName("configuration"); return cfgResponse; } + @Override + public ConfigurationGroupResponse createConfigurationGroupResponse(ConfigurationGroup cfgGroup) { + ConfigurationGroupResponse cfgGroupResponse = new ConfigurationGroupResponse(); + cfgGroupResponse.setGroupName(cfgGroup.getName()); + cfgGroupResponse.setDescription(cfgGroup.getDescription()); + cfgGroupResponse.setPrecedence(cfgGroup.getPrecedence()); + + List subgroups = _configMgr.getConfigurationSubGroups(cfgGroup.getId()); + List cfgSubGroupResponses = new ArrayList<>(); + for (ConfigurationSubGroup subgroup : subgroups) { + ConfigurationSubGroupResponse cfgSubGroupResponse = createConfigurationSubGroupResponse(subgroup); + cfgSubGroupResponses.add(cfgSubGroupResponse); + } + cfgGroupResponse.setSubGroups(cfgSubGroupResponses); + cfgGroupResponse.setObjectName("configurationgroup"); + return cfgGroupResponse; + } + + private ConfigurationSubGroupResponse createConfigurationSubGroupResponse(ConfigurationSubGroup cfgSubGroup) { + ConfigurationSubGroupResponse cfgSubGroupResponse = new ConfigurationSubGroupResponse(); + cfgSubGroupResponse.setSubGroupName(cfgSubGroup.getName()); + cfgSubGroupResponse.setPrecedence(cfgSubGroup.getPrecedence()); + cfgSubGroupResponse.setObjectName("subgroup"); + return cfgSubGroupResponse; + } + @Override public SnapshotResponse createSnapshotResponse(Snapshot snapshot) { SnapshotResponse snapshotResponse = new SnapshotResponse(); diff --git a/server/src/main/java/com/cloud/configuration/Config.java b/server/src/main/java/com/cloud/configuration/Config.java index c07115d87c2..aeefdb58f8c 100644 --- a/server/src/main/java/com/cloud/configuration/Config.java +++ b/server/src/main/java/com/cloud/configuration/Config.java @@ -54,6 +54,8 @@ public enum Config { "alert.email.addresses", null, "Comma separated list of email addresses which are going to receive alert emails.", + null, + ConfigKey.Kind.CSV, null), AlertEmailSender("Alert", ManagementServer.class, String.class, "alert.email.sender", null, "Sender of alert email (will be in the From header of the email).", null), AlertSMTPHost("Alert", ManagementServer.class, String.class, "alert.smtp.host", null, "SMTP hostname used for sending out email alerts.", null), @@ -229,7 +231,9 @@ public enum Config { "network.loadbalancer.haproxy.stats.visibility", "global", "Load Balancer(haproxy) stats visibility, the value can be one of the following six parameters : global,guest-network,link-local,disabled,all,default", - null), + null, + ConfigKey.Kind.Select, + "global,guest-network,link-local,disabled,all,default"), NetworkLBHaproxyStatsUri( "Network", ManagementServer.class, @@ -354,6 +358,8 @@ public enum Config { "network.dhcp.nondefaultnetwork.setgateway.guestos", "Windows", "The guest OS's name start with this fields would result in DHCP server response gateway information even when the network it's on is not default network. Names are separated by comma.", + null, + ConfigKey.Kind.CSV, null), //VPN @@ -425,8 +431,22 @@ public enum Config { "8001", "Console proxy command port that is used to communicate with management server", null), - ConsoleProxyRestart("Console Proxy", AgentManager.class, Boolean.class, "consoleproxy.restart", "true", "Console proxy restart flag, defaulted to true", null), - ConsoleProxyUrlDomain("Console Proxy", AgentManager.class, String.class, "consoleproxy.url.domain", "", "Console proxy url domain", "domainName", "privateip"), + ConsoleProxyRestart( + "Console Proxy", + AgentManager.class, + Boolean.class, + "consoleproxy.restart", + "true", + "Console proxy restart flag, defaulted to true", + null), + ConsoleProxyUrlDomain( + "Console Proxy", + AgentManager.class, + String.class, + "consoleproxy.url.domain", + "", + "Console proxy url domain", + "domainName,privateip"), ConsoleProxySessionMax( "Console Proxy", AgentManager.class, @@ -649,7 +669,9 @@ public enum Config { HypervisorType.Hyperv + "," + HypervisorType.KVM + "," + HypervisorType.XenServer + "," + HypervisorType.VMware + "," + HypervisorType.BareMetal + "," + HypervisorType.Ovm + "," + HypervisorType.LXC + "," + HypervisorType.Ovm3, "The list of hypervisors that this deployment will use.", - "hypervisorList"), + "hypervisorList", + ConfigKey.Kind.CSV, + null), ManagementNetwork("Advanced", ManagementServer.class, String.class, "management.network.cidr", null, "The cidr of management server network", null), EventPurgeDelay( "Advanced", @@ -903,7 +925,9 @@ public enum Config { "vm.allocation.algorithm", "random", "'random', 'firstfit', 'userdispersing', 'userconcentratedpod_random', 'userconcentratedpod_firstfit', 'firstfitleastconsumed' : Order in which hosts within a cluster will be considered for VM/volume allocation.", - null), + null, + ConfigKey.Kind.Select, + "random,firstfit,userdispersing,userconcentratedpod_random,userconcentratedpod_firstfit,firstfitleastconsumed"), VmDeploymentPlanner( "Advanced", ManagementServer.class, @@ -911,7 +935,9 @@ public enum Config { "vm.deployment.planner", "FirstFitPlanner", "'FirstFitPlanner', 'UserDispersingPlanner', 'UserConcentratedPodPlanner': DeploymentPlanner heuristic that will be used for VM deployment.", - null), + null, + ConfigKey.Kind.Select, + "FirstFitPlanner,UserDispersingPlanner,UserConcentratedPodPlanner"), ElasticLoadBalancerEnabled( "Advanced", ManagementServer.class, @@ -1061,6 +1087,8 @@ public enum Config { "xenserver.pvdriver.version", "xenserver61", "default Xen PV driver version for registered template, valid value:xenserver56,xenserver61 ", + "xenserver56,xenserver61", + ConfigKey.Kind.Select, "xenserver56,xenserver61"), XenServerHotFix("Advanced", ManagementServer.class, @@ -1127,7 +1155,9 @@ public enum Config { "vmware.root.disk.controller", "ide", "Specify the default disk controller for root volumes, valid values are scsi, ide, osdefault. Please check documentation for more details on each of these values.", - null), + null, + ConfigKey.Kind.Select, + "scsi,ide,osdefault"), VmwareSystemVmNicDeviceType( "Advanced", ManagementServer.class, @@ -1135,7 +1165,9 @@ public enum Config { "vmware.systemvm.nic.device.type", "E1000", "Specify the default network device type for system VMs, valid values are E1000, PCNet32, Vmxnet2, Vmxnet3", - null), + null, + ConfigKey.Kind.Select, + "E1000,PCNet32,Vmxnet2,Vmxnet3"), VmwareRecycleHungWorker( "Advanced", ManagementServer.class, @@ -1217,7 +1249,7 @@ public enum Config { TrafficSentinelIncludeZones( "Usage", ManagementServer.class, - Integer.class, + String.class, "traffic.sentinel.include.zones", "EXTERNAL", "Traffic going into specified list of zones is metered. For metering all traffic leave this parameter empty", @@ -1225,7 +1257,7 @@ public enum Config { TrafficSentinelExcludeZones( "Usage", ManagementServer.class, - Integer.class, + String.class, "traffic.sentinel.exclude.zones", "", "Traffic going into specified list of zones is not metered.", @@ -1361,6 +1393,8 @@ public enum Config { "network.dns.basiczone.updates", "all", "This parameter can take 2 values: all (default) and pod. It defines if DHCP/DNS requests have to be send to all dhcp servers in cloudstack, or only to the one in the same pod", + "all,pod", + ConfigKey.Kind.Select, "all,pod"), ClusterMessageTimeOutSeconds( @@ -1783,8 +1817,10 @@ public enum Config { private final String _name; private final String _defaultValue; private final String _description; - private final String[] _range; + private final String _range; private final String _scope; // Parameter can be at different levels (Zone/cluster/pool/account), by default every parameter is at global + private final ConfigKey.Kind _kind; + private final String _options; private static final HashMap> s_scopeLevelConfigsMap = new HashMap>(); static { @@ -1833,7 +1869,11 @@ public enum Config { } } - private Config(String category, Class componentClass, Class type, String name, String defaultValue, String description, String... range) { + private Config(String category, Class componentClass, Class type, String name, String defaultValue, String description, String range) { + this(category, componentClass, type, name, defaultValue, description, range, null, null); + } + + private Config(String category, Class componentClass, Class type, String name, String defaultValue, String description, String range, ConfigKey.Kind kind, String options) { _category = category; _componentClass = componentClass; _type = type; @@ -1842,6 +1882,8 @@ public enum Config { _description = description; _range = range; _scope = ConfigKey.Scope.Global.toString(); + _kind = kind; + _options = options; } public String getCategory() { @@ -1868,6 +1910,17 @@ public enum Config { return _scope; } + public String getKind() { + if (_kind == null) { + return null; + } + return _kind.toString(); + } + + public String getOptions() { + return _options; + } + public String getComponent() { if (_componentClass == ManagementServer.class) { return "management-server"; @@ -1896,7 +1949,7 @@ public enum Config { } } - public String[] getRange() { + public String getRange() { return _range; } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 0f9aca452da..2f2419eef83 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -97,6 +97,10 @@ import org.apache.cloudstack.framework.config.ConfigDepot; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao; +import org.apache.cloudstack.framework.config.impl.ConfigurationGroupVO; +import org.apache.cloudstack.framework.config.impl.ConfigurationSubGroupVO; import org.apache.cloudstack.framework.config.impl.ConfigurationVO; import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.MessageSubscriber; @@ -296,6 +300,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject ConfigurationDao _configDao; @Inject + ConfigurationGroupDao _configGroupDao; + @Inject + ConfigurationSubGroupDao _configSubGroupDao; + @Inject ConfigDepot _configDepot; @Inject HostPodDao _podDao; @@ -1232,10 +1240,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati return null; } - final String[] range = configuration.getRange(); - if (range == null) { + if (configuration.getRange() == null) { return null; } + String[] range = configuration.getRange().split(","); if (type.equals(String.class)) { return validateIfStringValueIsInRange(name, value, range); @@ -7563,6 +7571,91 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati }; } + @Override + public String getConfigurationType(final String configName) { + final ConfigurationVO cfg = _configDao.findByName(configName); + if (cfg == null) { + s_logger.warn("Configuration " + configName + " not found"); + return Configuration.ValueType.String.name(); + } + + if (weightBasedParametersForValidation.contains(configName)) { + return Configuration.ValueType.Range.name(); + } + + Class type = null; + final Config c = Config.getConfig(configName); + if (c == null) { + s_logger.warn("Configuration " + configName + " no found. Perhaps moved to ConfigDepot"); + final ConfigKey configKey = _configDepot.get(configName); + if (configKey == null) { + s_logger.warn("Couldn't find configuration " + configName + " in ConfigDepot too."); + return Configuration.ValueType.String.name(); + } + type = configKey.type(); + } else { + type = c.getType(); + } + + return getInputType(type, cfg); + } + + private String getInputType(Class type, ConfigurationVO cfg) { + if (type == null) { + return Configuration.ValueType.String.name(); + } + + if (type == String.class || type == Character.class) { + if (cfg.getKind() == null) { + return Configuration.ValueType.String.name(); + } + return cfg.getKind(); + } + if (type == Integer.class || type == Long.class || type == Short.class) { + return Configuration.ValueType.Number.name(); + } + if (type == Float.class || type == Double.class) { + return Configuration.ValueType.Decimal.name(); + } + if (type == Boolean.class) { + return Configuration.ValueType.Boolean.name(); + } + return Configuration.ValueType.String.name(); + } + + @Override + public Pair getConfigurationGroupAndSubGroup(final String configName) { + if (StringUtils.isBlank(configName)) { + throw new CloudRuntimeException("Empty configuration name provided"); + } + + final ConfigurationVO cfg = _configDao.findByName(configName); + if (cfg == null) { + s_logger.warn("Configuration " + configName + " not found"); + throw new InvalidParameterValueException("configuration with name " + configName + " doesn't exist"); + } + + String groupName = "Miscellaneous"; + String subGroupName = "Others"; + ConfigurationSubGroupVO configSubGroup = _configSubGroupDao.findById(cfg.getSubGroupId()); + if (configSubGroup != null) { + subGroupName = configSubGroup.getName(); + } + + ConfigurationGroupVO configGroup = _configGroupDao.findById(cfg.getGroupId()); + if (configGroup != null) { + groupName = configGroup.getName(); + } + + return new Pair(groupName, subGroupName); + } + + @Override + public List getConfigurationSubGroups(final Long groupId) { + List configSubGroups = _configSubGroupDao.findByGroup(groupId); + return configSubGroups; + } + static class ParamCountPair { private Long id; private int paramCount; diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java index efd15a4283b..8b2fd81bb48 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java @@ -63,7 +63,7 @@ public interface VirtualNetworkApplianceManager extends Manager, VirtualNetworkA static final ConfigKey RouterTemplateOvm3 = new ConfigKey(String.class, RouterTemplateOvm3CK, "Advanced", "SystemVM Template (Ovm3)", "Name of the default router template on Ovm3.", true, ConfigKey.Scope.Zone, null); - static final ConfigKey SetServiceMonitor = new ConfigKey(String.class, SetServiceMonitorCK, "Advanced", "true", + static final ConfigKey SetServiceMonitor = new ConfigKey(Boolean.class, SetServiceMonitorCK, "Advanced", "true", "service monitoring in router enable/disable option, default true", true, ConfigKey.Scope.Zone, null); static final ConfigKey RouterAlertsCheckInterval = new ConfigKey(Integer.class, RouterAlertsCheckIntervalCK, "Advanced", "1800", @@ -84,36 +84,36 @@ public interface VirtualNetworkApplianceManager extends Manager, VirtualNetworkA true, ConfigKey.Scope.Global, null); static final ConfigKey RouterHealthChecksBasicInterval = new ConfigKey(Integer.class, "router.health.checks.basic.interval", "Advanced", "3", "Interval in minutes at which basic router health checks are performed. If set to 0, no tests are scheduled.", - true, ConfigKey.Scope.Global, null); + true, ConfigKey.Scope.Global, null, RouterHealthChecksEnabled.key()); static final ConfigKey RouterHealthChecksAdvancedInterval = new ConfigKey(Integer.class, "router.health.checks.advanced.interval", "Advanced", "10", "Interval in minutes at which advanced router health checks are performed. If set to 0, no tests are scheduled.", - true, ConfigKey.Scope.Global, null); + true, ConfigKey.Scope.Global, null, RouterHealthChecksEnabled.key()); static final ConfigKey RouterHealthChecksConfigRefreshInterval = new ConfigKey(Integer.class, RouterHealthChecksConfigRefreshIntervalCK, "Advanced", "10", "Interval in minutes at which router health checks config - such as scheduling intervals, excluded checks, etc is updated on virtual routers by the management server. This value should" + " be sufficiently high (like 2x) from the router.health.checks.basic.interval and router.health.checks.advanced.interval so that there is time between new results generation and results generation for passed data.", - false, ConfigKey.Scope.Global, null); + false, ConfigKey.Scope.Global, null, RouterHealthChecksEnabled.key()); static final ConfigKey RouterHealthChecksResultFetchInterval = new ConfigKey(Integer.class, RouterHealthChecksResultFetchIntervalCK, "Advanced", "10", "Interval in minutes at which router health checks results are fetched by management server. On each result fetch, management server evaluates need to recreate VR as per configuration of " + RouterHealthChecksFailuresToRecreateVrCK + "This value should be sufficiently high (like 2x) from the router.health.checks.basic.interval and router.health.checks.advanced.interval so that there is time between new results generation and fetch.", - false, ConfigKey.Scope.Global, null); + false, ConfigKey.Scope.Global, null, RouterHealthChecksEnabled.key()); static final ConfigKey RouterHealthChecksFailuresToRecreateVr = new ConfigKey(String.class, RouterHealthChecksFailuresToRecreateVrCK, "Advanced", "", "Health checks failures defined by this config are the checks that should cause router recreation. If empty the recreate is not attempted for any health check failure. Possible values are comma separated script names " + "from systemvm’s /root/health_scripts/ (namely - cpu_usage_check.py, dhcp_check.py, disk_space_check.py, dns_check.py, gateways_check.py, haproxy_check.py, iptables_check.py, memory_usage_check.py, router_version_check.py), connectivity.test, filesystem.writable.test " + " or services (namely - loadbalancing.service, webserver.service, dhcp.service) ", - true, ConfigKey.Scope.Zone, null); + true, ConfigKey.Scope.Zone, null, null, RouterHealthChecksEnabled.key(), null, null, ConfigKey.Kind.CSV, null); static final ConfigKey RouterHealthChecksToExclude = new ConfigKey(String.class, "router.health.checks.to.exclude", "Advanced", "", "Health checks that should be excluded when executing scheduled checks on the router. This can be a comma separated list of script names placed in the '/root/health_checks/' folder. Currently the following scripts are " + "placed in default systemvm template - cpu_usage_check.py, disk_space_check.py, gateways_check.py, iptables_check.py, router_version_check.py, dhcp_check.py, dns_check.py, haproxy_check.py, memory_usage_check.py.", - true, ConfigKey.Scope.Zone, null); + true, ConfigKey.Scope.Zone, null, null, RouterHealthChecksEnabled.key(), null, null, ConfigKey.Kind.CSV, null); static final ConfigKey RouterHealthChecksFreeDiskSpaceThreshold = new ConfigKey(Double.class, "router.health.checks.free.disk.space.threshold", "Advanced", "100", "Free disk space threshold (in MB) on VR below which the check is considered a failure.", - true, ConfigKey.Scope.Zone, null); + true, ConfigKey.Scope.Zone, null, RouterHealthChecksEnabled.key()); static final ConfigKey RouterHealthChecksMaxCpuUsageThreshold = new ConfigKey(Double.class, "router.health.checks.max.cpu.usage.threshold", "Advanced", "100", " Max CPU Usage threshold as % above which check is considered a failure.", - true, ConfigKey.Scope.Zone, null); + true, ConfigKey.Scope.Zone, null, RouterHealthChecksEnabled.key()); static final ConfigKey RouterHealthChecksMaxMemoryUsageThreshold = new ConfigKey(Double.class, "router.health.checks.max.memory.usage.threshold", "Advanced", "100", "Max Memory Usage threshold as % above which check is considered a failure.", - true, ConfigKey.Scope.Zone, null); + true, ConfigKey.Scope.Zone, null, RouterHealthChecksEnabled.key()); ConfigKey RouterLogrotateFrequency = new ConfigKey<>(String.class, "router.logrotate.frequency", "Advanced", "*:00:00", "Sets the frequency of the logrotate service on the virtual router. The default value is *:00:00 (hourly) and follows the last block of " + "OnCalendar standard [Hour:Minute:Second]. e.g, *:*:00 is for every minute and */12:00:00 is for every 12 hours. See Systemd Timers for more options. " + diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index 24581aa1ee7..a0e8a2a2f0d 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -1862,10 +1862,10 @@ Configurable, StateListener configGroupAndSubGroup = _configDepotAdmin.getConfigurationGroupAndSubGroupByName(name); + configVO.setGroupId(configGroupAndSubGroup.first()); + configVO.setSubGroupId(configGroupAndSubGroup.second()); + if (c.getKind() != null) { + configVO.setKind(c.getKind()); + } + if (c.getOptions() != null) { + configVO.setOptions(c.getOptions()); + } + _configDao.persist(configVO); } } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index d2375ef8d1c..1a2da837365 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -76,6 +76,7 @@ import org.apache.cloudstack.api.command.admin.cluster.AddClusterCmd; import org.apache.cloudstack.api.command.admin.cluster.DeleteClusterCmd; import org.apache.cloudstack.api.command.admin.cluster.ListClustersCmd; import org.apache.cloudstack.api.command.admin.cluster.UpdateClusterCmd; +import org.apache.cloudstack.api.command.admin.config.ListCfgGroupsByCmd; import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd; import org.apache.cloudstack.api.command.admin.config.ListDeploymentPlannersCmd; import org.apache.cloudstack.api.command.admin.config.ListHypervisorCapabilitiesCmd; @@ -582,6 +583,7 @@ import org.apache.cloudstack.api.command.user.vpn.UpdateVpnGatewayCmd; import org.apache.cloudstack.api.command.user.zone.ListZonesCmd; import org.apache.cloudstack.config.ApiServiceConfiguration; import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.config.ConfigurationGroup; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; @@ -589,6 +591,10 @@ import org.apache.cloudstack.framework.config.ConfigDepot; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao; +import org.apache.cloudstack.framework.config.impl.ConfigurationGroupVO; +import org.apache.cloudstack.framework.config.impl.ConfigurationSubGroupVO; import org.apache.cloudstack.framework.config.impl.ConfigurationVO; import org.apache.cloudstack.framework.security.keystore.KeystoreManager; import org.apache.cloudstack.managed.context.ManagedContextRunnable; @@ -858,6 +864,10 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe @Inject private ConfigurationDao _configDao; @Inject + private ConfigurationGroupDao _configGroupDao; + @Inject + private ConfigurationSubGroupDao _configSubGroupDao; + @Inject private ConsoleProxyManager _consoleProxyMgr; @Inject private SecondaryStorageVmManager _secStorageVmMgr; @@ -2117,6 +2127,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe final Long imageStoreId = cmd.getImageStoreId(); Long accountId = cmd.getAccountId(); Long domainId = cmd.getDomainId(); + final String groupName = cmd.getGroupName(); + final String subGroupName = cmd.getSubGroupName(); + final String parentName = cmd.getParentName(); String scope = null; Long id = null; int paramCountCheck = 0; @@ -2186,6 +2199,29 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe sc.addAnd("name", SearchCriteria.Op.LIKE, "%" + name + "%"); } + if (groupName != null) { + ConfigurationGroupVO configGroupVO = _configGroupDao.findByName(groupName); + if (configGroupVO == null) { + throw new InvalidParameterValueException("Invalid configuration group: " + groupName); + } + Long groupId = configGroupVO.getId(); + sc.addAnd("groupId", SearchCriteria.Op.EQ, groupId); + } + + if (subGroupName != null) { + ConfigurationSubGroupVO configSubGroupVO = _configSubGroupDao.findByName(subGroupName); + if (configSubGroupVO == null) { + throw new InvalidParameterValueException("Invalid configuration subgroup: " + subGroupName); + } + + Long subGroupId = configSubGroupVO.getId(); + sc.addAnd("subGroupId", SearchCriteria.Op.EQ, subGroupId); + } + + if (parentName != null) { + sc.addAnd("parent", SearchCriteria.Op.EQ, parentName); + } + if (category != null) { sc.addAnd("category", SearchCriteria.Op.EQ, category); } @@ -2235,6 +2271,20 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe return new Pair, Integer>(result.first(), result.second()); } + @Override + public Pair, Integer> listConfigurationGroups(ListCfgGroupsByCmd cmd) { + final Filter searchFilter = new Filter(ConfigurationGroupVO.class, "precedence", true, null, null); + final SearchCriteria sc = _configGroupDao.createSearchCriteria(); + + final String groupName = cmd.getGroupName(); + if (StringUtils.isNotBlank(groupName)) { + sc.addAnd("name", SearchCriteria.Op.EQ, groupName); + } + + final Pair, Integer> result = _configGroupDao.searchAndCount(sc, searchFilter); + return new Pair, Integer>(result.first(), result.second()); + } + @Override public Pair, Integer> searchForIPAddresses(final ListPublicIpAddressesCmd cmd) { final Long associatedNetworkId = cmd.getAssociatedNetworkId(); @@ -3221,6 +3271,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(ListClustersCmd.class); cmdList.add(UpdateClusterCmd.class); cmdList.add(ListCfgsByCmd.class); + cmdList.add(ListCfgGroupsByCmd.class); cmdList.add(ListHypervisorCapabilitiesCmd.class); cmdList.add(UpdateCfgCmd.class); cmdList.add(UpdateHypervisorCapabilitiesCmd.class); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index ea72a38cbb9..37815bb71f4 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -624,14 +624,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private static final ConfigKey EnableAdditionalVmConfig = new ConfigKey<>("Advanced", Boolean.class, "enable.additional.vm.configuration", "false", "allow additional arbitrary configuration to vm", true, ConfigKey.Scope.Account); - private static final ConfigKey KvmAdditionalConfigAllowList = new ConfigKey<>("Advanced", String.class, - "allow.additional.vm.configuration.list.kvm", "", "Comma separated list of allowed additional configuration options.", true); + private static final ConfigKey KvmAdditionalConfigAllowList = new ConfigKey<>(String.class, + "allow.additional.vm.configuration.list.kvm", "Advanced", "", "Comma separated list of allowed additional configuration options.", true, ConfigKey.Scope.Global, null, null, EnableAdditionalVmConfig.key(), null, null, ConfigKey.Kind.CSV, null); - private static final ConfigKey XenServerAdditionalConfigAllowList = new ConfigKey<>("Advanced", String.class, - "allow.additional.vm.configuration.list.xenserver", "", "Comma separated list of allowed additional configuration options", true); + private static final ConfigKey XenServerAdditionalConfigAllowList = new ConfigKey<>(String.class, + "allow.additional.vm.configuration.list.xenserver", "Advanced", "", "Comma separated list of allowed additional configuration options", true, ConfigKey.Scope.Global, null, null, EnableAdditionalVmConfig.key(), null, null, ConfigKey.Kind.CSV, null); - private static final ConfigKey VmwareAdditionalConfigAllowList = new ConfigKey<>("Advanced", String.class, - "allow.additional.vm.configuration.list.vmware", "", "Comma separated list of allowed additional configuration options.", true); + private static final ConfigKey VmwareAdditionalConfigAllowList = new ConfigKey<>(String.class, + "allow.additional.vm.configuration.list.vmware", "Advanced", "", "Comma separated list of allowed additional configuration options.", true, ConfigKey.Scope.Global, null, null, EnableAdditionalVmConfig.key(), null, null, ConfigKey.Kind.CSV, null); private static final ConfigKey VmDestroyForcestop = new ConfigKey("Advanced", Boolean.class, "vm.destroy.forcestop", "false", "On destroy, force-stop takes this value ", true); diff --git a/server/src/main/java/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImpl.java b/server/src/main/java/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImpl.java index b93f2b340d9..1414a94907e 100644 --- a/server/src/main/java/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImpl.java @@ -50,10 +50,10 @@ import org.apache.commons.lang3.StringUtils; public class IndirectAgentLBServiceImpl extends ComponentLifecycleBase implements IndirectAgentLB, Configurable { public static final Logger LOG = Logger.getLogger(IndirectAgentLBServiceImpl.class); - public static final ConfigKey IndirectAgentLBAlgorithm = new ConfigKey<>("Advanced", String.class, - "indirect.agent.lb.algorithm", "static", + public static final ConfigKey IndirectAgentLBAlgorithm = new ConfigKey<>(String.class, + "indirect.agent.lb.algorithm", "Advanced", "static", "The algorithm to be applied on the provided 'host' management server list that is sent to indirect agents. Allowed values are: static, roundrobin and shuffle.", - true, ConfigKey.Scope.Global); + true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, "static,roundrobin,shuffle"); public static final ConfigKey IndirectAgentLBCheckInterval = new ConfigKey<>("Advanced", Long.class, "indirect.agent.lb.check.interval", "0", diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java index 3777996c7eb..4ebbc91d5e5 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java @@ -106,7 +106,7 @@ public class DiagnosticsServiceImpl extends ManagerBase implements PluggableServ "Enable the garbage collector background task to delete old files from secondary storage.", false); private static final ConfigKey GarbageCollectionInterval = new ConfigKey<>("Advanced", Integer.class, "diagnostics.data.gc.interval", "86400", - "The interval at which the garbage collector background tasks in seconds", false); + "The interval at which the garbage collector background tasks in seconds", false, EnableGarbageCollector.key()); // These are easily computed properties and need not need a restart of the management server private static final ConfigKey DataRetrievalTimeout = new ConfigKey<>("Advanced", Long.class, @@ -114,7 +114,7 @@ public class DiagnosticsServiceImpl extends ManagerBase implements PluggableServ "Overall system VM script execution time out in seconds.", true); private static final ConfigKey MaximumFileAgeforGarbageCollection = new ConfigKey<>("Advanced", Long.class, "diagnostics.data.max.file.age", "86400", - "Sets the maximum time in seconds a file can stay in secondary storage before it is deleted.", true); + "Sets the maximum time in seconds a file can stay in secondary storage before it is deleted.", true, EnableGarbageCollector.key()); private static final ConfigKey DiskQuotaPercentageThreshold = new ConfigKey<>("Advanced", Double.class, "diagnostics.data.disable.threshold", "0.9", "Sets the secondary storage disk utilisation percentage for file retrieval. " + diff --git a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java index cd9baa9f5d2..420edb640dc 100644 --- a/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java +++ b/server/src/main/java/org/apache/cloudstack/diagnostics/fileprocessor/DiagnosticsFilesList.java @@ -30,18 +30,18 @@ public interface DiagnosticsFilesList { * in the system vm and grab output for retrieval, e.g. the output from iptables-save is written to a file * which will then be retrieved. */ - ConfigKey SystemVMDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, - "diagnostics.data.systemvm.defaults", "iptables,ipaddr,iprule,iproute,/etc/cloudstack-release," + + ConfigKey SystemVMDefaultSupportedFiles = new ConfigKey<>(String.class, + "diagnostics.data.systemvm.defaults", "Advanced", "iptables,ipaddr,iprule,iproute,/etc/cloudstack-release," + "/usr/local/cloud/systemvm/conf/agent.properties,/usr/local/cloud/systemvm/conf/consoleproxy.properties," + "/var/log/cloud.log,/var/log/patchsystemvm.log,/var/log/daemon.log", - "List of supported diagnostics data file options for the CPVM and SSVM.", true); + "List of supported diagnostics data file options for the CPVM and SSVM.", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null); - ConfigKey RouterDefaultSupportedFiles = new ConfigKey<>("Advanced", String.class, - "diagnostics.data.router.defaults", "iptables,ipaddr,iprule,iproute,/etc/cloudstack-release," + + ConfigKey RouterDefaultSupportedFiles = new ConfigKey<>(String.class, + "diagnostics.data.router.defaults", "Advanced", "iptables,ipaddr,iprule,iproute,/etc/cloudstack-release," + "/etc/dnsmasq.conf,/etc/dhcphosts.txt,/etc/dhcpopts.txt,/etc/dnsmasq.d/cloud.conf,/etc/dnsmasq-resolv.conf,/var/lib/misc/dnsmasq.leases,/var/log/dnsmasq.log," + "/etc/hosts,/etc/resolv.conf,/etc/haproxy/haproxy.cfg,/var/log/haproxy.log,/etc/ipsec.d/l2tp.conf,/var/log/cloud.log," + "/var/log/routerServiceMonitor.log,/var/log/daemon.log", - "List of supported diagnostics data file options for the domain router.", true); + "List of supported diagnostics data file options for the domain router.", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null); List generateFileList(); } diff --git a/server/src/test/async-job-component.xml b/server/src/test/async-job-component.xml index 413194c6718..d1b0ca600d7 100644 --- a/server/src/test/async-job-component.xml +++ b/server/src/test/async-job-component.xml @@ -81,6 +81,8 @@ + + diff --git a/server/src/test/java/com/cloud/configuration/ConfigTest.java b/server/src/test/java/com/cloud/configuration/ConfigTest.java index 1c9b2e71972..ca0c26dd222 100644 --- a/server/src/test/java/com/cloud/configuration/ConfigTest.java +++ b/server/src/test/java/com/cloud/configuration/ConfigTest.java @@ -24,7 +24,7 @@ public class ConfigTest { for (Config configuration : Config.values()) { if (configuration.getType().equals(Integer.class) && configuration.getRange() != null) { try { - final String[] options = configuration.getRange()[0].split("-"); + final String[] options = configuration.getRange().split("-"); final int min = Integer.parseInt(options[0]); final int max = Integer.parseInt(options[1]); if (options.length != 2) { diff --git a/server/src/test/java/com/cloud/network/security/SecurityGroupManagerTestConfiguration.java b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerTestConfiguration.java index a5940cd779a..f6136e31049 100644 --- a/server/src/test/java/com/cloud/network/security/SecurityGroupManagerTestConfiguration.java +++ b/server/src/test/java/com/cloud/network/security/SecurityGroupManagerTestConfiguration.java @@ -31,6 +31,8 @@ import org.springframework.core.type.filter.TypeFilter; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl; import org.apache.cloudstack.test.utils.SpringUtils; import com.cloud.agent.AgentManager; @@ -70,8 +72,8 @@ import com.cloud.vm.dao.UserVmDetailsDaoImpl; import com.cloud.vm.dao.VMInstanceDaoImpl; @Configuration -@ComponentScan(basePackageClasses = {SecurityGroupRulesDaoImpl.class, UserVmDaoImpl.class, AccountDaoImpl.class, ConfigurationDaoImpl.class, - SecurityGroupWorkDaoImpl.class, VmRulesetLogDaoImpl.class, VMInstanceDaoImpl.class, DomainDaoImpl.class, UsageEventDaoImpl.class, +@ComponentScan(basePackageClasses = {SecurityGroupRulesDaoImpl.class, UserVmDaoImpl.class, AccountDaoImpl.class, ConfigurationDaoImpl.class, ConfigurationGroupDaoImpl.class, + ConfigurationSubGroupDaoImpl.class, SecurityGroupWorkDaoImpl.class, VmRulesetLogDaoImpl.class, VMInstanceDaoImpl.class, DomainDaoImpl.class, UsageEventDaoImpl.class, ResourceTagsDaoImpl.class, HostDaoImpl.class, HostDetailsDaoImpl.class, HostTagsDaoImpl.class, ClusterDaoImpl.class, HostPodDaoImpl.class, DataCenterDaoImpl.class, DataCenterIpAddressDaoImpl.class, HostTransferMapDaoImpl.class, SecurityGroupManagerImpl2.class, SecurityGroupDaoImpl.class, SecurityGroupVMMapDaoImpl.class, UserVmDetailsDaoImpl.class, DataCenterIpAddressDaoImpl.class, DataCenterLinkLocalIpAddressDaoImpl.class, diff --git a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java index bb6487fb118..84165869778 100644 --- a/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java +++ b/server/src/test/java/com/cloud/vm/FirstFitPlannerTest.java @@ -37,6 +37,8 @@ import org.apache.cloudstack.framework.config.ConfigDepot; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ScopedConfigStorage; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao; import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; import org.apache.cloudstack.framework.config.impl.ConfigurationVO; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; @@ -116,6 +118,10 @@ public class FirstFitPlannerTest { @Inject ConfigurationDao configDao; @Inject + ConfigurationGroupDao configGroupDao; + @Inject + ConfigurationSubGroupDao configSubGroupDao; + @Inject CapacityDao capacityDao; @Inject AccountManager accountMgr; @@ -431,6 +437,16 @@ public class FirstFitPlannerTest { return Mockito.mock(ConfigurationDao.class); } + @Bean + public ConfigurationGroupDao configurationGroupDao() { + return Mockito.mock(ConfigurationGroupDao.class); + } + + @Bean + public ConfigurationSubGroupDao configurationSubGroupDao() { + return Mockito.mock(ConfigurationSubGroupDao.class); + } + @Bean public PrimaryDataStoreDao primaryDataStoreDao() { return Mockito.mock(PrimaryDataStoreDao.class); diff --git a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java index 56f2428dd55..27361d5b7b3 100644 --- a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -56,6 +56,7 @@ import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd; import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd; import org.apache.cloudstack.api.command.user.network.ListNetworkOfferingsCmd; import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.framework.config.impl.ConfigurationSubGroupVO; import org.apache.cloudstack.region.PortableIp; import org.apache.cloudstack.region.PortableIpRange; import org.springframework.stereotype.Component; @@ -661,4 +662,21 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu return null; } + @Override + public String getConfigurationType(String configName) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Pair getConfigurationGroupAndSubGroup(String configName) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getConfigurationSubGroups(Long groupId) { + // TODO Auto-generated method stub + return null; + } } diff --git a/server/src/test/java/com/cloud/vpc/VpcTestConfiguration.java b/server/src/test/java/com/cloud/vpc/VpcTestConfiguration.java index 2dfbb3799b8..285a7ae4523 100644 --- a/server/src/test/java/com/cloud/vpc/VpcTestConfiguration.java +++ b/server/src/test/java/com/cloud/vpc/VpcTestConfiguration.java @@ -30,6 +30,8 @@ import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.TypeFilter; import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl; import org.apache.cloudstack.test.utils.SpringUtils; import com.cloud.alert.AlertManager; @@ -107,9 +109,9 @@ import com.cloud.vpc.dao.MockVpcOfferingDaoImpl; import com.cloud.vpc.dao.MockVpcOfferingServiceMapDaoImpl; @Configuration -@ComponentScan(basePackageClasses = {VpcManagerImpl.class, NetworkElement.class, VpcOfferingDao.class, ConfigurationDaoImpl.class, IPAddressDaoImpl.class, - DomainRouterDaoImpl.class, VpcGatewayDaoImpl.class, PrivateIpDaoImpl.class, StaticRouteDaoImpl.class, PhysicalNetworkDaoImpl.class, - ResourceTagsDaoImpl.class, FirewallRulesDaoImpl.class, VlanDaoImpl.class, AccountDaoImpl.class, ResourceCountDaoImpl.class, +@ComponentScan(basePackageClasses = {VpcManagerImpl.class, NetworkElement.class, VpcOfferingDao.class, ConfigurationDaoImpl.class, ConfigurationGroupDaoImpl.class, + ConfigurationSubGroupDaoImpl.class, IPAddressDaoImpl.class, DomainRouterDaoImpl.class, VpcGatewayDaoImpl.class, PrivateIpDaoImpl.class, StaticRouteDaoImpl.class, + PhysicalNetworkDaoImpl.class, ResourceTagsDaoImpl.class, FirewallRulesDaoImpl.class, VlanDaoImpl.class, AccountDaoImpl.class, ResourceCountDaoImpl.class, Site2SiteVpnGatewayDaoImpl.class, PodVlanMapDaoImpl.class, AccountVlanMapDaoImpl.class, DomainVlanMapDaoImpl.class, HostDaoImpl.class, HostDetailsDaoImpl.class, HostTagsDaoImpl.class, HostTransferMapDaoImpl.class, ClusterDaoImpl.class, HostPodDaoImpl.class, RouterNetworkDaoImpl.class, UserStatisticsDaoImpl.class, PhysicalNetworkTrafficTypeDaoImpl.class, FirewallRulesCidrsDaoImpl.class, ResourceLimitManagerImpl.class, diff --git a/server/src/test/java/com/cloud/vpc/dao/MockConfigurationDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockConfigurationDaoImpl.java index 94f8d659d28..d82a5d8e75a 100644 --- a/server/src/test/java/com/cloud/vpc/dao/MockConfigurationDaoImpl.java +++ b/server/src/test/java/com/cloud/vpc/dao/MockConfigurationDaoImpl.java @@ -19,7 +19,6 @@ package com.cloud.vpc.dao; import java.util.HashMap; import java.util.Map; - import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.impl.ConfigurationVO; diff --git a/server/src/test/resources/createNetworkOffering.xml b/server/src/test/resources/createNetworkOffering.xml index 214fa29cd75..0f558d11a7a 100644 --- a/server/src/test/resources/createNetworkOffering.xml +++ b/server/src/test/resources/createNetworkOffering.xml @@ -70,4 +70,6 @@ + + diff --git a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java index 68ed368483b..75bcbf2496a 100644 --- a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java +++ b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java @@ -267,7 +267,7 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar private final GlobalLock _allocLock = GlobalLock.getInternLock(getAllocLockName()); static final ConfigKey NTPServerConfig = new ConfigKey(String.class, "ntp.server.list", "Advanced", null, - "Comma separated list of NTP servers to configure in Secondary storage VM", true, ConfigKey.Scope.Global, null); + "Comma separated list of NTP servers to configure in Secondary storage VM", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null); static final ConfigKey MaxNumberOfSsvmsForMigration = new ConfigKey("Advanced", Integer.class, "max.ssvm.count", "5", "Number of additional SSVMs to handle migration of data objects concurrently", true, ConfigKey.Scope.Global); diff --git a/test/integration/smoke/test_global_settings.py b/test/integration/smoke/test_global_settings.py index c0dec787cc4..2018384ab3a 100644 --- a/test/integration/smoke/test_global_settings.py +++ b/test/integration/smoke/test_global_settings.py @@ -75,3 +75,109 @@ class TestUpdateConfigWithScope(cloudstackTestCase): updateConfigurationCmd.scopename = "zone" updateConfigurationCmd.scopeid = 1 self.apiClient.updateConfiguration(updateConfigurationCmd) + +class TestListConfigurations(cloudstackTestCase): + """ + Test to list configurations (global settings) + """ + @classmethod + def setUpClass(cls): + cls.apiclient = cls.testClient.getApiClient() + cls._cleanup = [] + + @classmethod + def tearDownClass(cls): + super(TestListConfigurations, cls).tearDownClass() + + def setUp(self): + self.apiClient = self.testClient.getApiClient() + self.cleanup = [] + + def tearDown(self): + super(TestListConfigurations, self).tearDown() + + @attr(tags=["devcloud", "basic", "advanced"], required_hardware="false") + def test_01_list_configs(self): + """ + test list configuration setting at global level + @return: + """ + listConfigurationsCmd = listConfigurations.listConfigurationsCmd() + + listConfigurationsCmd.name = "agent.lb.enabled" + listConfigurationsResponse = self.apiClient.listConfigurations(listConfigurationsCmd) + self.assertNotEqual(len(listConfigurationsResponse), 0, "Check if the list configurations API returns a non-empty response") + self.debug("The parameter %s listed with value %s" %(listConfigurationsCmd.name, listConfigurationsResponse[0].value)) + self.assertEqual(listConfigurationsResponse[0].type, 'Boolean', "Wrong type for the config") + self.assertEqual(listConfigurationsResponse[0].defaultvalue, 'false', "Wrong default value for the config") + self.assertEqual(listConfigurationsResponse[0].group, 'Management Server', "Check the group for the config") + self.assertEqual(listConfigurationsResponse[0].subgroup, 'Agent', "Check the subgroup for the config") + + listConfigurationsCmd.name = "storage.cleanup.interval" + listConfigurationsResponse = self.apiClient.listConfigurations(listConfigurationsCmd) + self.assertNotEqual(len(listConfigurationsResponse), 0, "Check if the list configurations API returns a non-empty response") + self.debug("The parameter %s listed with value %s" % (listConfigurationsCmd.name, listConfigurationsResponse[0].value)) + self.assertEqual(listConfigurationsResponse[0].type, 'Number', "Wrong type for the config") + self.assertEqual(listConfigurationsResponse[0].defaultvalue, '86400', "Wrong default value for the config") + self.assertEqual(listConfigurationsResponse[0].group, 'Infrastructure', "Check the group for the config") + self.assertEqual(listConfigurationsResponse[0].subgroup, 'Primary Storage', "Check the subgroup for the config") + + listConfigurationsCmd.name = "agent.load.threshold" + listConfigurationsResponse = self.apiClient.listConfigurations(listConfigurationsCmd) + self.assertNotEqual(len(listConfigurationsResponse), 0, "Check if the list configurations API returns a non-empty response") + self.debug("The parameter %s listed with value %s" % (listConfigurationsCmd.name, listConfigurationsResponse[0].value)) + self.assertEqual(listConfigurationsResponse[0].type, 'Range', "Wrong type for the config") + self.assertEqual(listConfigurationsResponse[0].defaultvalue, '0.7', "Wrong default value for the config") + self.assertEqual(listConfigurationsResponse[0].group, 'Management Server', "Check the group for the config") + self.assertEqual(listConfigurationsResponse[0].subgroup, 'Agent', "Check the subgroup for the config") + + listConfigurationsCmd.name = "endpoint.url" + listConfigurationsResponse = self.apiClient.listConfigurations(listConfigurationsCmd) + self.assertNotEqual(len(listConfigurationsResponse), 0, "Check if the list configurations API returns a non-empty response") + self.debug("The parameter %s listed with value %s" % (listConfigurationsCmd.name, listConfigurationsResponse[0].value)) + self.assertEqual(listConfigurationsResponse[0].type, 'String', "Wrong type for the config") + self.assertEqual(listConfigurationsResponse[0].defaultvalue, 'http://localhost:8080/client/api', "Wrong default value for the config") + + @attr(tags=["devcloud", "basic", "advanced"], required_hardware="false") + def test_02_list_config_parent(self): + """ + test list configuration setting parent + @return: + """ + listConfigurationsCmd = listConfigurations.listConfigurationsCmd() + + listConfigurationsCmd.name = "api.throttling.cachesize" + listConfigurationsResponse = self.apiClient.listConfigurations(listConfigurationsCmd) + self.assertNotEqual(len(listConfigurationsResponse), 0, "Check if the list configurations API returns a non-empty response") + self.assertEqual(listConfigurationsResponse[0].parent, 'api.throttling.enabled', "Wrong parent for the config") + + listConfigurationsCmd.name = "storage.cache.replacement.interval" + listConfigurationsResponse = self.apiClient.listConfigurations(listConfigurationsCmd) + self.assertNotEqual(len(listConfigurationsResponse), 0, "Check if the list configurations API returns a non-empty response") + self.assertEqual(listConfigurationsResponse[0].parent, 'storage.cache.replacement.enabled', "Wrong parent for the config") + + listConfigurationsCmd.name = "cloud.kubernetes.cluster.max.size" + listConfigurationsResponse = self.apiClient.listConfigurations(listConfigurationsCmd) + self.assertNotEqual(len(listConfigurationsResponse), 0, "Check if the list configurations API returns a non-empty response") + self.assertEqual(listConfigurationsResponse[0].parent, 'cloud.kubernetes.service.enabled', "Wrong parent for the config") + + @attr(tags=["devcloud", "basic", "advanced"], required_hardware="false") + def test_03_config_groups(self): + """ + test list configuration groups + @return: + """ + listConfigurationGroupsResponse = Configurations.listGroups(self.apiclient) + self.assertNotEqual(len(listConfigurationGroupsResponse), 0, "Check if the list configurationgroups API returns a non-empty response") + + self.debug("Total %d configuration groups listed" %(len(listConfigurationGroupsResponse))) + self.debug("Configuration groups: %s" % (str(listConfigurationGroupsResponse))) + + group = listConfigurationGroupsResponse[0].name + subgroup = listConfigurationGroupsResponse[0].subgroup[0].name + + listConfigurationsResponse = Configurations.list(self.apiclient, + group=group, + subgroup=subgroup) + self.assertNotEqual(len(listConfigurationsResponse), 0, "Check if the list configurations API returns a non-empty response") + self.debug("Total %d configurations for group %s, subgroup %s" % (len(listConfigurationsResponse), group, subgroup)) diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index c9633210aa0..22864f9cf7b 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -4653,6 +4653,14 @@ class Configurations: return (apiclient.listCapabilities(cmd)) + @classmethod + def listGroups(cls, apiclient, **kwargs): + """Lists configuration groups""" + cmd = listConfigurationGroups.listConfigurationGroupsCmd() + [setattr(cmd, k, v) for k, v in list(kwargs.items())] + return (apiclient.listConfigurationGroups(cmd)) + + @classmethod def reset(cls, apiclient, name, zoneid=None, clusterid=None, storageid=None, domainid=None, accountid=None): """Resets the specified configuration to original value""" diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 5f17374e17c..0725cb60577 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -778,7 +778,7 @@ "label.fwdeviceid": "ID", "label.fwdevicestate": "Status", "label.gateway": "Gateway", -"label.global.settings": "Global settings", +"label.global.settings": "Global Settings", "label.globo.dns": "GloboDNS", "label.globo.dns.configuration": "GloboDNS configuration", "label.glustervolume": "Volume", @@ -2598,6 +2598,7 @@ "message.scaleup.policy.name.continue": "Please input a name to ScaleUp policy to continue", "message.select.a.zone": "A zone typically corresponds to a single datacenter. Multiple zones help make the cloud more reliable by providing physical isolation and redundancy.", "message.select.affinity.groups": "Please select any affinity groups you want this VM to belong to:", +"message.select.deselect.to.sort": "Please select / deselect to sort the values", "message.select.destination.image.stores": "Please select Image Store(s) to which data is to be migrated to", "message.select.disk.offering": "Please select a disk offering for disk", "message.select.end.date.and.time": "Select an end date & time.", @@ -2720,6 +2721,7 @@ "message.template.type.change.warning": "WARNING: Changing the template type to SYSTEM will disable further changes to the template.", "message.tooltip.reserved.system.netmask": "The network prefix that defines the pod subnet. Uses CIDR notation.", "message.traffic.type.to.basic.zone": "traffic type to basic zone", +"message.type.values.to.add": "Please add additonal values by typing them in", "message.update.autoscale.policy.failed": "Failed to update autoscale policy", "message.update.autoscale.vmgroup.failed": "Failed to update autoscale vm group", "message.update.autoscale.vm.profile.failed": "Failed to update autoscale vm profile", diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 6111300881c..c1508e8211e 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -105,6 +105,7 @@ {{ text }} {{ text }} + {{ text }} {{ $t(text.toLowerCase()) }} {{ $t(text.toLowerCase()) }} diff --git a/ui/src/config/section/config.js b/ui/src/config/section/config.js index 5dc3a76e88a..c7e097277e2 100644 --- a/ui/src/config/section/config.js +++ b/ui/src/config/section/config.js @@ -26,7 +26,9 @@ export default { title: 'label.global.settings', icon: 'setting-outlined', permission: ['listConfigurations'], - columns: ['name', 'description', 'category', 'value', 'actions'] + listView: true, + popup: true, + component: () => import('@/views/setting/ConfigurationTab.vue') }, { name: 'ldapsetting', diff --git a/ui/src/style/vars.less b/ui/src/style/vars.less index 0c3bff4cbda..67a584453d1 100644 --- a/ui/src/style/vars.less +++ b/ui/src/style/vars.less @@ -400,6 +400,10 @@ a { background-color: #f9f9f9; } +.child-row { + background-color: #f5f5f5; +} + .tag-disabled-input, .btn-add-tag { background-color: #fff; } diff --git a/ui/src/views/setting/ConfigurationHierarchy.vue b/ui/src/views/setting/ConfigurationHierarchy.vue new file mode 100644 index 00000000000..c3cfddb763d --- /dev/null +++ b/ui/src/views/setting/ConfigurationHierarchy.vue @@ -0,0 +1,90 @@ +// 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. + + + + diff --git a/ui/src/views/setting/ConfigurationTab.vue b/ui/src/views/setting/ConfigurationTab.vue new file mode 100644 index 00000000000..e327bbe8e38 --- /dev/null +++ b/ui/src/views/setting/ConfigurationTab.vue @@ -0,0 +1,365 @@ +// 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. + + + + + + diff --git a/ui/src/views/setting/ConfigurationTable.vue b/ui/src/views/setting/ConfigurationTable.vue new file mode 100644 index 00000000000..d8182514376 --- /dev/null +++ b/ui/src/views/setting/ConfigurationTable.vue @@ -0,0 +1,191 @@ +// 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. + + + + + + diff --git a/ui/src/views/setting/ConfigurationValue.vue b/ui/src/views/setting/ConfigurationValue.vue new file mode 100644 index 00000000000..0069896f7a5 --- /dev/null +++ b/ui/src/views/setting/ConfigurationValue.vue @@ -0,0 +1,363 @@ +// 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. + + + + + diff --git a/usage/src/test/java/com/cloud/usage/UsageManagerTestConfiguration.java b/usage/src/test/java/com/cloud/usage/UsageManagerTestConfiguration.java index 26b610656c7..d0e153b0b83 100644 --- a/usage/src/test/java/com/cloud/usage/UsageManagerTestConfiguration.java +++ b/usage/src/test/java/com/cloud/usage/UsageManagerTestConfiguration.java @@ -30,6 +30,8 @@ import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.TypeFilter; import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDaoImpl; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDaoImpl; import org.apache.cloudstack.test.utils.SpringUtils; import com.cloud.alert.AlertManager; @@ -66,7 +68,7 @@ import com.cloud.user.dao.UserStatisticsDaoImpl; @ComponentScan(basePackageClasses = {AccountDaoImpl.class, UsageDaoImpl.class, UsageJobDaoImpl.class, UsageVMInstanceDaoImpl.class, UsageIPAddressDaoImpl.class, UsageNetworkDaoImpl.class, UsageVolumeDaoImpl.class, UsageStorageDaoImpl.class, UsageLoadBalancerPolicyDaoImpl.class, UsagePortForwardingRuleDaoImpl.class, UsageNetworkOfferingDaoImpl.class, UsageVPNUserDaoImpl.class, UsageVmDiskDaoImpl.class, - UsageSecurityGroupDaoImpl.class, ConfigurationDaoImpl.class, UsageManagerImpl.class, VMInstanceUsageParser.class, IPAddressUsageParser.class, + UsageSecurityGroupDaoImpl.class, ConfigurationDaoImpl.class, ConfigurationGroupDaoImpl.class, ConfigurationSubGroupDaoImpl.class, UsageManagerImpl.class, VMInstanceUsageParser.class, IPAddressUsageParser.class, LoadBalancerUsageParser.class, NetworkOfferingUsageParser.class, NetworkUsageParser.class, PortForwardingUsageParser.class, SecurityGroupUsageParser.class, StorageUsageParser.class, VmDiskUsageParser.class, VolumeUsageParser.class, VPNUserUsageParser.class, UserStatisticsDaoImpl.class},