|
16 | 16 | //under the License. |
17 | 17 | package org.apache.cloudstack.api.response; |
18 | 18 |
|
| 19 | +import java.io.IOException; |
19 | 20 | import java.lang.reflect.Field; |
20 | 21 | import java.lang.reflect.Modifier; |
21 | 22 | import java.lang.reflect.ParameterizedType; |
|
34 | 35 | import java.util.Iterator; |
35 | 36 | import java.util.List; |
36 | 37 | import java.util.ListIterator; |
| 38 | +import java.util.Map; |
| 39 | +import java.util.Set; |
37 | 40 | import java.util.function.Consumer; |
38 | 41 | import java.util.stream.Collectors; |
39 | 42 |
|
40 | 43 | import javax.inject.Inject; |
41 | 44 |
|
42 | 45 | import com.cloud.utils.DateUtil; |
| 46 | +import com.cloud.utils.exception.CloudRuntimeException; |
43 | 47 | import org.apache.cloudstack.api.ApiErrorCode; |
44 | 48 | import org.apache.cloudstack.api.ServerApiException; |
45 | 49 | import org.apache.cloudstack.api.command.QuotaBalanceCmd; |
|
51 | 55 | import org.apache.cloudstack.api.command.QuotaTariffCreateCmd; |
52 | 56 | import org.apache.cloudstack.api.command.QuotaTariffListCmd; |
53 | 57 | import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd; |
| 58 | +import org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd; |
54 | 59 | import org.apache.cloudstack.context.CallContext; |
55 | 60 | import org.apache.cloudstack.discovery.ApiDiscoveryService; |
| 61 | +import org.apache.cloudstack.jsinterpreter.JsInterpreterHelper; |
56 | 62 | import org.apache.cloudstack.quota.QuotaManager; |
57 | 63 | import org.apache.cloudstack.quota.QuotaManagerImpl; |
58 | 64 | import org.apache.cloudstack.quota.QuotaService; |
|
78 | 84 | import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; |
79 | 85 | import org.apache.cloudstack.quota.vo.QuotaTariffVO; |
80 | 86 | import org.apache.cloudstack.quota.vo.QuotaUsageVO; |
| 87 | +import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter; |
81 | 88 | import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; |
82 | 89 | import org.apache.commons.lang3.StringUtils; |
83 | 90 | import org.apache.commons.lang3.reflect.FieldUtils; |
@@ -133,11 +140,13 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { |
133 | 140 | private QuotaManager _quotaManager; |
134 | 141 | @Inject |
135 | 142 | private QuotaEmailConfigurationDao quotaEmailConfigurationDao; |
| 143 | + @Inject |
| 144 | + private JsInterpreterHelper jsInterpreterHelper; |
| 145 | + @Inject |
| 146 | + private ApiDiscoveryService apiDiscoveryService; |
136 | 147 |
|
137 | 148 | private final Class<?>[] assignableClasses = {GenericPresetVariable.class, ComputingResources.class}; |
138 | 149 |
|
139 | | - @Inject |
140 | | - private ApiDiscoveryService apiDiscoveryService; |
141 | 150 |
|
142 | 151 | @Override |
143 | 152 | public QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO tariff, boolean returnActivationRule) { |
@@ -789,7 +798,7 @@ protected Class<?> getClassOfField(Field field){ |
789 | 798 | */ |
790 | 799 | public void filterSupportedTypes(List<Pair<String, String>> variables, QuotaTypes quotaType, PresetVariableDefinition presetVariableDefinitionAnnotation, Class<?> fieldClass, |
791 | 800 | String presetVariableName) { |
792 | | - if (Arrays.stream(presetVariableDefinitionAnnotation.supportedTypes()).noneMatch(supportedType -> |
| 801 | + if (quotaType != null && Arrays.stream(presetVariableDefinitionAnnotation.supportedTypes()).noneMatch(supportedType -> |
793 | 802 | supportedType == quotaType.getQuotaType() || supportedType == 0)) { |
794 | 803 | return; |
795 | 804 | } |
@@ -928,4 +937,82 @@ protected QuotaConfigureEmailResponse createQuotaConfigureEmailResponse(QuotaEma |
928 | 937 |
|
929 | 938 | return quotaConfigureEmailResponse; |
930 | 939 | } |
| 940 | + |
| 941 | + @Override |
| 942 | + public QuotaValidateActivationRuleResponse validateActivationRule(QuotaValidateActivationRuleCmd cmd) { |
| 943 | + String message; |
| 944 | + String activationRule = cmd.getActivationRule(); |
| 945 | + QuotaTypes quotaType = cmd.getQuotaType(); |
| 946 | + String quotaName = quotaType.getQuotaName(); |
| 947 | + List<Pair<String, String>> usageTypeVariablesAndDescriptions = new ArrayList<>(); |
| 948 | + |
| 949 | + addAllPresetVariables(PresetVariables.class, quotaType, usageTypeVariablesAndDescriptions, null); |
| 950 | + List<String> usageTypeVariables = usageTypeVariablesAndDescriptions.stream().map(Pair::first).collect(Collectors.toList()); |
| 951 | + |
| 952 | + try (JsInterpreter jsInterpreter = new JsInterpreter(QuotaConfig.QuotaActivationRuleTimeout.value())) { |
| 953 | + Map<String, String> newVariables = injectUsageTypeVariables(jsInterpreter, usageTypeVariables); |
| 954 | + String scriptToExecute = jsInterpreterHelper.replaceScriptVariables(activationRule, newVariables); |
| 955 | + jsInterpreter.executeScript(String.format("new Function(\"%s\")", scriptToExecute.replaceAll("\n", ""))); |
| 956 | + } catch (IOException | CloudRuntimeException e) { |
| 957 | + logger.error("Unable to execute activation rule due to: [{}].", e.getMessage(), e); |
| 958 | + message = "Error while executing activation rule. Check if there are no syntax errors and all variables are compatible with the given usage type."; |
| 959 | + return createValidateActivationRuleResponse(activationRule, quotaName, false, message); |
| 960 | + } |
| 961 | + |
| 962 | + Set<String> scriptVariables = jsInterpreterHelper.getScriptVariables(activationRule); |
| 963 | + if (isScriptVariablesValid(scriptVariables, usageTypeVariables)) { |
| 964 | + message = "The script has no syntax errors and all variables are compatible with the given usage type."; |
| 965 | + return createValidateActivationRuleResponse(activationRule, quotaName, true, message); |
| 966 | + } |
| 967 | + |
| 968 | + message = "Found variables that are not compatible with the given usage type."; |
| 969 | + return createValidateActivationRuleResponse(activationRule, quotaName, false, message); |
| 970 | + } |
| 971 | + |
| 972 | + /** |
| 973 | + * Checks whether script variables are compatible with the usage type. First, we remove all script variables that correspond to the script's usage type variables. |
| 974 | + * Then, returns true if none of the remaining script variables match any usage types variables, and false otherwise. |
| 975 | + * |
| 976 | + * @param scriptVariables Script variables. |
| 977 | + * @param scriptUsageTypeVariables Script usage type variables. |
| 978 | + * @return True if the script variables are valid, false otherwise. |
| 979 | + */ |
| 980 | + protected boolean isScriptVariablesValid(Set<String> scriptVariables, List<String> scriptUsageTypeVariables) { |
| 981 | + List<Pair<String, String>> allUsageTypeVariablesAndDescriptions = new ArrayList<>(); |
| 982 | + addAllPresetVariables(PresetVariables.class, null, allUsageTypeVariablesAndDescriptions, null); |
| 983 | + List<String> allUsageTypesVariables = allUsageTypeVariablesAndDescriptions.stream().map(Pair::first).collect(Collectors.toList()); |
| 984 | + |
| 985 | + List<String> matchVariables = scriptVariables.stream().filter(scriptUsageTypeVariables::contains).collect(Collectors.toList()); |
| 986 | + matchVariables.forEach(scriptVariables::remove); |
| 987 | + |
| 988 | + return scriptVariables.stream().noneMatch(allUsageTypesVariables::contains); |
| 989 | + } |
| 990 | + |
| 991 | + /** |
| 992 | + * Injects variables into JavaScript interpreter. It's necessary to remove all dots from the given variables, as the interpreter |
| 993 | + * does not interpret the variables as attributes of objects. |
| 994 | + * |
| 995 | + * @param jsInterpreter the {@link JsInterpreter} which the variables will be injected. |
| 996 | + * @param variables the {@link List} with variables to format and inject the formatted variables into interpreter. |
| 997 | + * @return A {@link Map} which has the key as the given variable and the value as the given variable formatted (without dots). |
| 998 | + */ |
| 999 | + protected Map<String, String> injectUsageTypeVariables(JsInterpreter jsInterpreter, List<String> variables) { |
| 1000 | + Map<String, String> formattedVariables = new HashMap<>(); |
| 1001 | + for (String variable : variables) { |
| 1002 | + String formattedVariable = variable.replace(".", ""); |
| 1003 | + formattedVariables.put(variable, formattedVariable); |
| 1004 | + jsInterpreter.injectVariable(formattedVariable, "false"); |
| 1005 | + } |
| 1006 | + |
| 1007 | + return formattedVariables; |
| 1008 | + } |
| 1009 | + |
| 1010 | + public QuotaValidateActivationRuleResponse createValidateActivationRuleResponse(String activationRule, String quotaType, Boolean isValid, String message) { |
| 1011 | + QuotaValidateActivationRuleResponse response = new QuotaValidateActivationRuleResponse(); |
| 1012 | + response.setActivationRule(activationRule); |
| 1013 | + response.setQuotaType(quotaType); |
| 1014 | + response.setValid(isValid); |
| 1015 | + response.setMessage(message); |
| 1016 | + return response; |
| 1017 | + } |
931 | 1018 | } |
0 commit comments