diff --git a/api/src/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java b/api/src/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java index aabfd4a620d..9f34405ffbd 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java @@ -23,8 +23,7 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseListCmd; import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.response.ConfigurationResponse; -import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.*; import org.apache.log4j.Logger; import com.cloud.configuration.Configuration; @@ -46,6 +45,13 @@ public class ListCfgsByCmd extends BaseListCmd { @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "lists configuration by name") private String configName; + @Parameter(name=ApiConstants.SCOPE, type = CommandType.STRING, description = "scope(zone/cluster/pool/account) of the parameter that needs to be updated") + private String scope; + + @Parameter(name=ApiConstants.ID, type = CommandType.UUID, entityType = {ZoneResponse.class, ClusterResponse.class, StoragePoolResponse.class, AccountResponse.class}, description = "corresponding ID of the scope") + private Long id; + + // /////////////////////////////////////////////////// // ///////////////// Accessors /////////////////////// // /////////////////////////////////////////////////// @@ -58,6 +64,15 @@ public class ListCfgsByCmd extends BaseListCmd { return configName; } + public String getScope() { + return scope; + } + + public Long getId() { + return id; + } + + @Override public Long getPageSizeVal() { Long pageSizeVal = 500L; @@ -85,6 +100,11 @@ public class ListCfgsByCmd extends BaseListCmd { for (Configuration cfg : result.first()) { ConfigurationResponse cfgResponse = _responseGenerator.createConfigurationResponse(cfg); cfgResponse.setObjectName("configuration"); + if (scope != null) { + cfgResponse.setScope(scope); + } else { + cfgResponse.setScope("global"); + } configResponses.add(cfgResponse); } diff --git a/api/src/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java b/api/src/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java index ffeb58621b9..074c5a3b028 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java @@ -22,7 +22,7 @@ import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.ConfigurationResponse; +import org.apache.cloudstack.api.response.*; import org.apache.log4j.Logger; import com.cloud.configuration.Configuration; @@ -43,6 +43,12 @@ public class UpdateCfgCmd extends BaseCmd { @Parameter(name=ApiConstants.VALUE, type=CommandType.STRING, description="the value of the configuration", length=4095) private String value; + @Parameter(name=ApiConstants.SCOPE, type = CommandType.STRING, description = "scope(zone/cluster/pool/account) of the parameter that needs to be updated") + private String scope; + + @Parameter(name=ApiConstants.ID, type = CommandType.UUID, entityType = {ZoneResponse.class, ClusterResponse.class, StoragePoolResponse.class, AccountResponse.class}, description = "corresponding ID of the scope") + private Long id; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -55,6 +61,14 @@ public class UpdateCfgCmd extends BaseCmd { return value; } + public String getScope() { + return scope; + } + + public Long getId() { + return id; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -75,6 +89,12 @@ public class UpdateCfgCmd extends BaseCmd { if (cfg != null) { ConfigurationResponse response = _responseGenerator.createConfigurationResponse(cfg); response.setResponseName(getCommandName()); + if (scope != null) { + response.setScope(scope); + response.setValue(value); + } else { + response.setScope("global"); + } this.setResponseObject(response); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update config"); diff --git a/api/src/org/apache/cloudstack/api/response/ConfigurationResponse.java b/api/src/org/apache/cloudstack/api/response/ConfigurationResponse.java index 95b8af2f4c5..176c47aff8b 100644 --- a/api/src/org/apache/cloudstack/api/response/ConfigurationResponse.java +++ b/api/src/org/apache/cloudstack/api/response/ConfigurationResponse.java @@ -32,9 +32,13 @@ public class ConfigurationResponse extends BaseResponse { @SerializedName(ApiConstants.VALUE) @Param(description="the value of the configuration") private String value; + @SerializedName(ApiConstants.SCOPE) @Param(description="scope(zone/cluster/pool/account) of the parameter that needs to be updated") + private String scope; + @SerializedName(ApiConstants.DESCRIPTION) @Param(description="the description of the configuration") private String description; + public String getCategory() { return category; } @@ -66,4 +70,12 @@ public class ConfigurationResponse extends BaseResponse { public void setDescription(String description) { this.description = description; } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } } diff --git a/api/test/org/apache/cloudstack/api/command/test/ListCfgCmdTest.java b/api/test/org/apache/cloudstack/api/command/test/ListCfgCmdTest.java new file mode 100644 index 00000000000..7c05eaf5a8d --- /dev/null +++ b/api/test/org/apache/cloudstack/api/command/test/ListCfgCmdTest.java @@ -0,0 +1,89 @@ +// 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.test; + +import com.cloud.configuration.Configuration; +import com.cloud.configuration.ConfigurationService; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.resource.ResourceService; +import com.cloud.server.ManagementService; +import com.cloud.utils.Pair; +import junit.framework.Assert; +import junit.framework.TestCase; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd; +import org.apache.cloudstack.api.response.ConfigurationResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; + +public class ListCfgCmdTest extends TestCase{ + + private ListCfgsByCmd listCfgsByCmd; + private ManagementService mgr; + private ResponseGenerator responseGenerator; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Before + public void setUp() { + responseGenerator = Mockito.mock(ResponseGenerator.class); + mgr = Mockito.mock(ManagementService.class); + listCfgsByCmd = new ListCfgsByCmd(); + } + + @Test + public void testCreateSuccess() { + + Configuration cfg = Mockito.mock(Configuration.class); + listCfgsByCmd._mgr = mgr; + listCfgsByCmd._responseGenerator = responseGenerator; + + + + List configList = new ArrayList(); + configList.add(cfg); + + Pair, Integer> result = new Pair, Integer>(configList, 1); + + try { + Mockito.when( + mgr.searchForConfigurations(listCfgsByCmd)) + .thenReturn(result); + }catch (Exception e){ + Assert.fail("Received exception when success expected " + e.getMessage()); + } + ConfigurationResponse cfgResponse = new ConfigurationResponse(); + cfgResponse.setName("Test case"); + Mockito.when(responseGenerator.createConfigurationResponse(cfg)).thenReturn(cfgResponse); + + listCfgsByCmd.execute(); + Mockito.verify(responseGenerator).createConfigurationResponse(cfg); + + ListResponse actualResponse = (ListResponse) listCfgsByCmd.getResponseObject(); + Assert.assertEquals(cfgResponse, actualResponse.getResponses().get(0)); + } + +} diff --git a/api/test/org/apache/cloudstack/api/command/test/UpdateCfgCmdTest.java b/api/test/org/apache/cloudstack/api/command/test/UpdateCfgCmdTest.java new file mode 100644 index 00000000000..27000cf1770 --- /dev/null +++ b/api/test/org/apache/cloudstack/api/command/test/UpdateCfgCmdTest.java @@ -0,0 +1,116 @@ +// 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.test; + +import com.cloud.configuration.Configuration; +import com.cloud.configuration.ConfigurationService; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.resource.ResourceService; +import junit.framework.Assert; +import junit.framework.TestCase; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; +import org.apache.cloudstack.api.response.ConfigurationResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; + +public class UpdateCfgCmdTest extends TestCase{ + + private UpdateCfgCmd updateCfgCmd; + private ConfigurationService configService; + private ResponseGenerator responseGenerator; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Before + public void setUp() { + responseGenerator = Mockito.mock(ResponseGenerator.class); + configService = Mockito.mock(ConfigurationService.class); + updateCfgCmd = new UpdateCfgCmd(); + } + + @Test + public void testExecuteForEmptyResult() { + updateCfgCmd._configService = configService; + + try { + updateCfgCmd.execute(); + } catch (ServerApiException exception) { + Assert.assertEquals("Failed to update config", + exception.getDescription()); + } + + } + + @Test + public void testExecuteForNullResult() { + + updateCfgCmd._configService = configService; + + try { + Mockito.when( + configService.updateConfiguration(updateCfgCmd)) + .thenReturn(null); + } catch (InvalidParameterValueException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + updateCfgCmd.execute(); + } catch (ServerApiException exception) { + Assert.assertEquals("Failed to update config", + exception.getDescription()); + } + + } + + + @Test + public void testCreateSuccess() { + + Configuration cfg = Mockito.mock(Configuration.class); + updateCfgCmd._configService = configService; + updateCfgCmd._responseGenerator = responseGenerator; + + try { + Mockito.when( + configService.updateConfiguration(updateCfgCmd)) + .thenReturn(cfg); + }catch (Exception e){ + Assert.fail("Received exception when success expected " + e.getMessage()); + } + + ConfigurationResponse response = new ConfigurationResponse(); + response.setName("Test case"); + Mockito.when(responseGenerator.createConfigurationResponse(cfg)).thenReturn(response); + + updateCfgCmd.execute(); + Mockito.verify(responseGenerator).createConfigurationResponse(cfg); + ConfigurationResponse actualResponse = (ConfigurationResponse) updateCfgCmd.getResponseObject(); + Assert.assertEquals(response, actualResponse); + Assert.assertEquals("updateconfigurationresponse", response.getResponseName()); + } + +} diff --git a/engine/api/src/org/apache/cloudstack/storage/datastore/db/StoragePoolDetailsDao.java b/engine/api/src/org/apache/cloudstack/storage/datastore/db/StoragePoolDetailsDao.java index be71670e992..237f235704d 100644 --- a/engine/api/src/org/apache/cloudstack/storage/datastore/db/StoragePoolDetailsDao.java +++ b/engine/api/src/org/apache/cloudstack/storage/datastore/db/StoragePoolDetailsDao.java @@ -25,4 +25,5 @@ public interface StoragePoolDetailsDao extends GenericDao details); Map getDetails(long poolId); + StoragePoolDetailVO findDetail(long poolId, String name); } diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 299396641d6..4d1185a973b 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -16,9 +16,7 @@ // under the License. package com.cloud.configuration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import java.util.*; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; @@ -339,7 +337,7 @@ public enum Config { //disabling lb as cluster sync does not work with distributed cluster AgentLbEnable("Advanced", ManagementServer.class, Boolean.class, "agent.lb.enabled", "false", "If agent load balancing enabled in cluster setup", null), SubDomainNetworkAccess("Advanced", NetworkManager.class, Boolean.class, "allow.subdomain.network.access", "true", "Allow subdomains to use networks dedicated to their parent domain(s)", null), - UseExternalDnsServers("Advanced", NetworkManager.class, Boolean.class, "use.external.dns", "false", "Bypass internal dns, use external dns1 and dns2", null), + UseExternalDnsServers("Advanced", NetworkManager.class, Boolean.class, "use.external.dns", "false", "Bypass internal dns, use external dns1 and dns2", null, ConfigurationParameterScope.zone.toString()), EncodeApiResponse("Advanced", ManagementServer.class, Boolean.class, "encode.api.response", "false", "Do URL encoding for the api response, false by default", null), DnsBasicZoneUpdates("Advanced", NetworkManager.class, String.class, "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"), @@ -412,6 +410,35 @@ public enum Config { private final String _defaultValue; private final String _description; 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 + + public static enum ConfigurationParameterScope { + global, + zone, + cluster, + pool, + account + } + + private static final HashMap> _scopeLevelConfigsMap = new HashMap>(); + static { + _scopeLevelConfigsMap.put(ConfigurationParameterScope.zone.toString(), new ArrayList()); + _scopeLevelConfigsMap.put(ConfigurationParameterScope.cluster.toString(), new ArrayList()); + _scopeLevelConfigsMap.put(ConfigurationParameterScope.pool.toString(), new ArrayList()); + _scopeLevelConfigsMap.put(ConfigurationParameterScope.account.toString(), new ArrayList()); + _scopeLevelConfigsMap.put(ConfigurationParameterScope.global.toString(), new ArrayList()); + + for (Config c : Config.values()) { + //Creating group of parameters per each level (zone/cluster/pool/account) + StringTokenizer tokens = new StringTokenizer(c.getScope(), ","); + while (tokens.hasMoreTokens()) { + String scope = tokens.nextToken().trim(); + List currentConfigs = _scopeLevelConfigsMap.get(scope); + currentConfigs.add(c); + _scopeLevelConfigsMap.put(scope, currentConfigs); + } + } + } private static final HashMap> _configs = new HashMap>(); static { @@ -447,6 +474,17 @@ public enum Config { _defaultValue = defaultValue; _description = description; _range = range; + _scope = ConfigurationParameterScope.global.toString(); + } + private Config(String category, Class componentClass, Class type, String name, String defaultValue, String description, String range, String scope) { + _category = category; + _componentClass = componentClass; + _type = type; + _name = name; + _defaultValue = defaultValue; + _description = description; + _range = range; + _scope = scope; } public String getCategory() { @@ -473,6 +511,10 @@ public enum Config { return _componentClass; } + public String getScope() { + return _scope; + } + public String getComponent() { if (_componentClass == ManagementServer.class) { return "management-server"; @@ -530,4 +572,8 @@ public enum Config { } return categories; } + + public static List getConfigListByScope(String scope) { + return _scopeLevelConfigsMap.get(scope); + } } diff --git a/server/src/com/cloud/configuration/ConfigurationManager.java b/server/src/com/cloud/configuration/ConfigurationManager.java index afaf0d6a2e5..738c5bab35d 100755 --- a/server/src/com/cloud/configuration/ConfigurationManager.java +++ b/server/src/com/cloud/configuration/ConfigurationManager.java @@ -60,7 +60,7 @@ public interface ConfigurationManager extends ConfigurationService, Manager { * @param name * @param value */ - void updateConfiguration(long userId, String name, String category, String value); + void updateConfiguration(long userId, String name, String category, String value, String scope, Long id); /** * Creates a new service offering diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 4fc2db7fc2a..d142ca6254d 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -39,6 +39,7 @@ import javax.naming.NamingException; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; +import com.cloud.dc.dao.*; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.api.ApiConstants.LDAPParams; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; @@ -78,20 +79,13 @@ import com.cloud.dc.DataCenter.NetworkType; import com.cloud.dc.DataCenterIpAddressVO; import com.cloud.dc.DataCenterLinkLocalIpAddressVO; import com.cloud.dc.DataCenterVO; +import com.cloud.dc.DcDetailVO; import com.cloud.dc.HostPodVO; import com.cloud.dc.Pod; import com.cloud.dc.PodVlanMapVO; import com.cloud.dc.Vlan; import com.cloud.dc.Vlan.VlanType; import com.cloud.dc.VlanVO; -import com.cloud.dc.dao.AccountVlanMapDao; -import com.cloud.dc.dao.ClusterDao; -import com.cloud.dc.dao.DataCenterDao; -import com.cloud.dc.dao.DataCenterIpAddressDao; -import com.cloud.dc.dao.DataCenterLinkLocalIpAddressDao; -import com.cloud.dc.dao.HostPodDao; -import com.cloud.dc.dao.PodVlanMapDao; -import com.cloud.dc.dao.VlanDao; import com.cloud.deploy.DataCenterDeployment; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; @@ -188,6 +182,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject DataCenterDao _zoneDao; @Inject + DcDetailsDao _zoneDetailsDao; + @Inject DomainDao _domainDao; @Inject SwiftDao _swiftDao; @@ -327,15 +323,37 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Override @DB - public void updateConfiguration(long userId, String name, String category, String value) { + public void updateConfiguration(long userId, String name, String category, String value, String scope, Long resourceId) { - String validationMsg = validateConfigurationValue(name, value); + String validationMsg = validateConfigurationValue(name, value, scope); if (validationMsg != null) { s_logger.error("Invalid configuration option, name: " + name + ", value:" + value); throw new InvalidParameterValueException(validationMsg); } + // If scope of the parameter is given then it needs to be updated in the corresponding details table, + // if scope is mentioned as global or not mentioned then it is normal global parameter updation + if (scope != null && !scope.isEmpty() && !Config.ConfigurationParameterScope.global.toString().equalsIgnoreCase(scope)) { + if (Config.ConfigurationParameterScope.zone.toString().equalsIgnoreCase(scope)) { + DataCenterVO zone = _zoneDao.findById(resourceId); + if (zone == null) { + throw new InvalidParameterValueException("unable to find zone by id " + resourceId); + } + DcDetailVO dcDetailVO = _zoneDetailsDao.findDetail(resourceId, name.toLowerCase()); + if (dcDetailVO == null) { + dcDetailVO = new DcDetailVO(dcDetailVO.getId(), name, value); + _zoneDetailsDao.persist(dcDetailVO); + } else { + dcDetailVO.setValue(value); + _zoneDetailsDao.update(resourceId, dcDetailVO); + } + } else { + s_logger.error("TO Do for the remaining levels (cluster/pool/account)"); + throw new InvalidParameterValueException("The scope "+ scope +" yet to be implemented"); + } + } + // Execute all updates in a single transaction Transaction txn = Transaction.currentTxn(); txn.start(); @@ -440,6 +458,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati Long userId = UserContext.current().getCallerUserId(); String name = cmd.getCfgName(); String value = cmd.getValue(); + String scope = cmd.getScope(); + Long id = cmd.getId(); UserContext.current().setEventDetails(" Name: " + name + " New Value: " + (((name.toLowerCase()).contains("password")) ? "*****" : (((value == null) ? "" : value)))); // check if config value exists @@ -456,7 +476,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati value = null; } - updateConfiguration(userId, name, config.getCategory(), value); + updateConfiguration(userId, name, config.getCategory(), value, scope, id); String updatedValue = _configDao.getValue(name); if ((value == null && updatedValue == null) || updatedValue.equalsIgnoreCase(value)) { return _configDao.findByName(name); @@ -466,13 +486,20 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } - private String validateConfigurationValue(String name, String value) { + private String validateConfigurationValue(String name, String value, String scope) { Config c = Config.getConfig(name); if (c == null) { s_logger.error("Missing configuration variable " + name + " in configuration table"); return "Invalid configuration variable."; } + String configScope = c.getScope(); + if (scope != null && !scope.isEmpty()) { + if (!configScope.contains(scope)) { + s_logger.error("Invalid scope " + scope + " for the parameter " + name); + return "Invalid scope for the parameter."; + } + } Class type = c.getType(); diff --git a/server/src/com/cloud/configuration/dao/ConfigurationDao.java b/server/src/com/cloud/configuration/dao/ConfigurationDao.java index c86c0243fae..2b099013430 100644 --- a/server/src/com/cloud/configuration/dao/ConfigurationDao.java +++ b/server/src/com/cloud/configuration/dao/ConfigurationDao.java @@ -17,6 +17,7 @@ package com.cloud.configuration.dao; import java.util.Map; +import java.util.List; import com.cloud.configuration.ConfigurationVO; import com.cloud.utils.db.GenericDao; diff --git a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index ab91059b0f3..4c7bc75b2d3 100755 --- a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -40,6 +40,7 @@ import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.server.ConfigurationServer; import org.apache.cloudstack.api.command.admin.router.UpgradeRouterCmd; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -286,6 +287,8 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V @Inject ConfigurationManager _configMgr; @Inject + ConfigurationServer _configServer; + @Inject ServiceOfferingDao _serviceOfferingDao = null; @Inject UserVmDao _userVmDao; @@ -2094,7 +2097,7 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V boolean useExtDns = !dnsProvided; /* For backward compatibility */ - String use_external_dns = _configDao.getValue(Config.UseExternalDnsServers.key()); + String use_external_dns = _configServer.getConfigValue(Config.UseExternalDnsServers.key(), Config.ConfigurationParameterScope.zone.toString(), dc.getId()); if (use_external_dns != null && use_external_dns.equals("true")) { useExtDns = true; } diff --git a/server/src/com/cloud/server/ConfigurationServer.java b/server/src/com/cloud/server/ConfigurationServer.java index f441b1f5de6..c1306d5ec82 100644 --- a/server/src/com/cloud/server/ConfigurationServer.java +++ b/server/src/com/cloud/server/ConfigurationServer.java @@ -16,6 +16,9 @@ // under the License. package com.cloud.server; +import java.util.List; + +import com.cloud.configuration.ConfigurationVO; import com.cloud.exception.InternalErrorException; /** @@ -30,4 +33,6 @@ public interface ConfigurationServer { */ public void persistDefaultValues() throws InternalErrorException; public void updateKeyPairs(); + public String getConfigValue(String name, String scope, Long resourceId); + public List getConfigListByScope(String scope, Long resourceId); } diff --git a/server/src/com/cloud/server/ConfigurationServerImpl.java b/server/src/com/cloud/server/ConfigurationServerImpl.java index c4da1ab337f..cd890ce8582 100755 --- a/server/src/com/cloud/server/ConfigurationServerImpl.java +++ b/server/src/com/cloud/server/ConfigurationServerImpl.java @@ -36,28 +36,29 @@ import java.util.Map; import java.util.Properties; import java.util.UUID; import java.util.regex.Pattern; +import java.util.StringTokenizer; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.configuration.*; +import com.cloud.dc.*; +import com.cloud.dc.dao.DcDetailsDao; +import com.cloud.user.*; +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; -import com.cloud.configuration.Config; -import com.cloud.configuration.ConfigurationVO; -import com.cloud.configuration.Resource; import com.cloud.configuration.Resource.ResourceOwnerType; import com.cloud.configuration.Resource.ResourceType; -import com.cloud.configuration.ResourceCountVO; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.configuration.dao.ResourceCountDao; import com.cloud.dc.DataCenter.NetworkType; -import com.cloud.dc.DataCenterVO; -import com.cloud.dc.HostPodVO; -import com.cloud.dc.VlanVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.HostPodDao; import com.cloud.dc.dao.VlanDao; @@ -91,9 +92,6 @@ import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.test.IPRangeConfig; -import com.cloud.user.Account; -import com.cloud.user.AccountVO; -import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.PasswordGenerator; import com.cloud.utils.PropertiesUtil; @@ -127,6 +125,11 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio @Inject private ResourceCountDao _resourceCountDao; @Inject private NetworkOfferingServiceMapDao _ntwkOfferingServiceMapDao; @Inject private IdentityDao _identityDao; + @Inject private DcDetailsDao _dcDetailsDao; + @Inject private ClusterDetailsDao _clusterDetailsDao; + @Inject private StoragePoolDetailsDao _storagePoolDetailsDao; + @Inject private AccountDetailsDao _accountDetailsDao; + public ConfigurationServerImpl() { setRunLevel(ComponentLifecycle.RUN_LEVEL_FRAMEWORK_BOOTSTRAP); @@ -672,6 +675,76 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio } } + @Override + public String getConfigValue(String name, String scope, Long resourceId) { + // If either of scope or resourceId is null then return global config value otherwise return value at the scope + Config c = Config.getConfig(name); + if (c == null) { + throw new CloudRuntimeException("Missing configuration variable " + name + " in configuration table"); + } + String configScope = c.getScope(); + if (scope != null && !scope.isEmpty()) { + if (!configScope.contains(scope)) { + throw new CloudRuntimeException("Invalid scope " + scope + " for the parameter " + name ); + } + if (resourceId != null) { + switch (Config.ConfigurationParameterScope.valueOf(scope)) { + case zone: DataCenterVO zone = _zoneDao.findById(resourceId); + if (zone == null) { + throw new InvalidParameterValueException("unable to find zone by id " + resourceId); + } + DcDetailVO dcDetailVO = _dcDetailsDao.findDetail(resourceId, name); + if (dcDetailVO != null && dcDetailVO.getValue() != null) { + return dcDetailVO.getValue(); + } break; + + case cluster: ClusterDetailsVO cluster = _clusterDetailsDao.findById(resourceId); + if (cluster == null) { + throw new InvalidParameterValueException("unable to find cluster by id " + resourceId); + } + ClusterDetailsVO clusterDetailsVO = _clusterDetailsDao.findDetail(resourceId, name); + if (clusterDetailsVO != null && clusterDetailsVO.getValue() != null) { + return clusterDetailsVO.getValue(); + } break; + + case pool: StoragePoolDetailVO pool = _storagePoolDetailsDao.findById(resourceId); + if (pool == null) { + throw new InvalidParameterValueException("unable to find storage pool by id " + resourceId); + } + StoragePoolDetailVO storagePoolDetailVO = _storagePoolDetailsDao.findDetail(resourceId, name); + if (storagePoolDetailVO != null && storagePoolDetailVO.getValue() != null) { + return storagePoolDetailVO.getValue(); + } break; + + case account: AccountDetailVO account = _accountDetailsDao.findById(resourceId); + if (account == null) { + throw new InvalidParameterValueException("unable to find account by id " + resourceId); + } + AccountDetailVO accountDetailVO = _accountDetailsDao.findDetail(resourceId, name); + if (accountDetailVO != null && accountDetailVO.getValue() != null) { + return accountDetailVO.getValue(); + } break; + default: + } + } + } + return _configDao.getValue(name); + } + + @Override + public List getConfigListByScope(String scope, Long resourceId) { + + // Getting the list of parameters defined at the scope + List configList = Config.getConfigListByScope(scope); + List configVOList = new ArrayList(); + for (Config param:configList){ + ConfigurationVO configVo = _configDao.findByName(param.toString()); + configVo.setValue(getConfigValue(param.toString(), scope, resourceId)); + configVOList.add(configVo); + } + return configVOList; + } + private void writeKeyToDisk(String key, String keyPath) { File keyfile = new File(keyPath); if (!keyfile.exists()) { diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 98f789a720a..db8db8a7194 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -44,6 +44,7 @@ import javax.crypto.spec.SecretKeySpec; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.configuration.*; import com.cloud.storage.dao.*; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.ApiConstants; @@ -106,10 +107,6 @@ import com.cloud.capacity.CapacityVO; import com.cloud.capacity.dao.CapacityDao; import com.cloud.capacity.dao.CapacityDaoImpl.SummedCapacity; import com.cloud.cluster.ClusterManager; -import com.cloud.configuration.Config; -import com.cloud.configuration.Configuration; -import com.cloud.configuration.ConfigurationManager; -import com.cloud.configuration.ConfigurationVO; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.consoleproxy.ConsoleProxyManagementState; import com.cloud.consoleproxy.ConsoleProxyManager; @@ -381,6 +378,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe @Inject S3Manager _s3Mgr; + @Inject + ConfigurationServer _configServer; + private final ScheduledExecutorService _eventExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("EventChecker")); private final ScheduledExecutorService _alertExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("AlertChecker")); private KeystoreManager _ksMgr; @@ -1038,6 +1038,22 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe Object name = cmd.getConfigName(); Object category = cmd.getCategory(); Object keyword = cmd.getKeyword(); + Long id = cmd.getId(); + String scope = cmd.getScope(); + + if (scope!= null && !scope.isEmpty()) { + // getting the list of parameters at requested scope + try { + Config.ConfigurationParameterScope.valueOf(scope.toLowerCase()); + } catch (Exception e ) { + throw new InvalidParameterValueException("Invalid scope " + scope + " while listing configuration parameters"); + } + if (id == null) { + throw new InvalidParameterValueException("Invalid id null, id is needed corresponding to the scope"); + } + List configList = _configServer.getConfigListByScope(scope, id); + return new Pair, Integer>(configList, configList.size()); + } if (keyword != null) { SearchCriteria ssc = _configDao.createSearchCriteria(); diff --git a/server/src/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java b/server/src/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java index 0d797ed3545..a0d5d0e6e97 100644 --- a/server/src/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java +++ b/server/src/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java @@ -72,4 +72,13 @@ public class StoragePoolDetailsDaoImpl extends GenericDaoBase sc = PoolSearch.create(); + sc.setParameters("pool", poolId); + sc.setParameters("name", name); + + return findOneIncludingRemovedBy(sc); + } } diff --git a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java index 9443815dc87..6cda294fbe7 100755 --- a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -25,6 +25,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import javax.naming.NamingException; +import com.cloud.configuration.ConfigurationVO; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; import org.apache.cloudstack.api.command.admin.ldap.LDAPConfigCmd; import org.apache.cloudstack.api.command.admin.ldap.LDAPRemoveCmd; @@ -425,9 +426,9 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu * @see com.cloud.configuration.ConfigurationManager#updateConfiguration(long, java.lang.String, java.lang.String, java.lang.String) */ @Override - public void updateConfiguration(long userId, String name, String category, String value) { + public void updateConfiguration(long userId, String name, String category, String value, String scope, Long resourceId) { // TODO Auto-generated method stub - + } /* (non-Javadoc) diff --git a/test/integration/smoke/test_UpdateCfg.py b/test/integration/smoke/test_UpdateCfg.py new file mode 100644 index 00000000000..be501b956d2 --- /dev/null +++ b/test/integration/smoke/test_UpdateCfg.py @@ -0,0 +1,85 @@ +# 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. +""" P1 tests for updating the granular Configuration parameter with scope and resource id provided. +""" +#Import Local Modules +import marvin +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.remoteSSHClient import remoteSSHClient +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * +from nose.plugins.attrib import attr +#Import System modules +import unittest +import hashlib +import random + +class TestUpdateConfigWithScope(cloudstackTestCase): + """ + This test updates the value of a configuration parameter + which is at zone level(scope) + """ + def setUp(self): + """ + CloudStack internally saves its passwords in md5 form and that is how we + specify it in the API. Python's hashlib library helps us to quickly hash + strings as follows + """ + mdf = hashlib.md5() + mdf.update('password') + mdf_pass = mdf.hexdigest() + + self.apiClient = self.testClient.getApiClient() #Get ourselves an API client + + + + def test_UpdateConfigParamWithScope(self): + + updateConfigurationCmd = updateConfiguration.updateConfigurationCmd() + updateConfigurationCmd.name = "use.external.dns" + updateConfigurationCmd.value = "true" + updateConfigurationCmd.scope = "zone" + updateConfigurationCmd.id = 1 + + updateConfigurationResponse = self.apiClient.updateConfiguration(updateConfigurationCmd) + self.debug("updated the parameter %s with value %s"%(updateConfigurationResponse.name, updateConfigurationResponse.value)) + + listConfigurationsCmd = listConfigurations.listConfigurationsCmd() + listConfigurationsCmd.cfgName = updateConfigurationResponse.name + listConfigurationsCmd.scope = "zone" + listConfigurationsCmd.id = 1 + listConfigurationsResponse = self.apiClient.listConfigurations(listConfigurationsCmd) + + self.assertNotEqual(len(listConfigurationsResponse), 0, "Check if the list API \ + returns a non-empty response") + + configParam = listConfigurationsResponse[0] + + self.assertEqual(configParam.value, updateConfigurationResponse.value, "Check if the update API returned \ + is the same as the one we got in the list API") + + + def tearDown(self): + + updateConfigurationCmd = updateConfiguration.updateConfigurationCmd() + updateConfigurationCmd.name = "use.external.dns" + updateConfigurationCmd.value = "false" + updateConfigurationCmd.scope = "zone" + updateConfigurationCmd.id = 1 + self.apiClient.updateConfiguration(updateConfigurationCmd)