java/com/google/cloud/deploymentmanager/autogen/SpecValidations.java (695 lines of code) (raw):

// Copyright 2018 Google LLC // // Licensed 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.google.cloud.deploymentmanager.autogen; import static com.google.common.base.Preconditions.checkArgument; import com.google.cloud.deploymentmanager.autogen.proto.AcceleratorSpec; import com.google.cloud.deploymentmanager.autogen.proto.ApplicationStatusSpec; import com.google.cloud.deploymentmanager.autogen.proto.BooleanExpression; import com.google.cloud.deploymentmanager.autogen.proto.BooleanExpression.BooleanDeployInputField; import com.google.cloud.deploymentmanager.autogen.proto.DeployInputField; import com.google.cloud.deploymentmanager.autogen.proto.DeployInputField.EmailBox; import com.google.cloud.deploymentmanager.autogen.proto.DeployInputField.GroupedBooleanCheckbox; import com.google.cloud.deploymentmanager.autogen.proto.DeployInputField.IntegerBox; import com.google.cloud.deploymentmanager.autogen.proto.DeployInputField.IntegerDropdown; import com.google.cloud.deploymentmanager.autogen.proto.DeployInputField.StringBox; import com.google.cloud.deploymentmanager.autogen.proto.DeployInputField.StringDropdown; import com.google.cloud.deploymentmanager.autogen.proto.DeployInputField.TypeCase; import com.google.cloud.deploymentmanager.autogen.proto.DeployInputSection; import com.google.cloud.deploymentmanager.autogen.proto.DeployInputSection.Placement; import com.google.cloud.deploymentmanager.autogen.proto.DeployInputSpec; import com.google.cloud.deploymentmanager.autogen.proto.DiskSpec; import com.google.cloud.deploymentmanager.autogen.proto.ExternalIpSpec; import com.google.cloud.deploymentmanager.autogen.proto.FirewallRuleSpec; import com.google.cloud.deploymentmanager.autogen.proto.FirewallRuleSpec.TrafficSource; import com.google.cloud.deploymentmanager.autogen.proto.GceMetadataItem; import com.google.cloud.deploymentmanager.autogen.proto.GceMetadataItem.ValueSpecCase; import com.google.cloud.deploymentmanager.autogen.proto.GceStartupScriptSpec; import com.google.cloud.deploymentmanager.autogen.proto.GcpAuthScopeSpec; import com.google.cloud.deploymentmanager.autogen.proto.ImageSpec; import com.google.cloud.deploymentmanager.autogen.proto.InstanceUrlSpec; import com.google.cloud.deploymentmanager.autogen.proto.MachineTypeSpec; import com.google.cloud.deploymentmanager.autogen.proto.MultiVmDeploymentPackageSpec; import com.google.cloud.deploymentmanager.autogen.proto.NetworkInterfacesSpec; import com.google.cloud.deploymentmanager.autogen.proto.PasswordSpec; import com.google.cloud.deploymentmanager.autogen.proto.PostDeployInfo; import com.google.cloud.deploymentmanager.autogen.proto.PostDeployInfo.ConnectToInstanceSpec; import com.google.cloud.deploymentmanager.autogen.proto.SingleVmDeploymentPackageSpec; import com.google.cloud.deploymentmanager.autogen.proto.StackdriverSpec; import com.google.cloud.deploymentmanager.autogen.proto.VmTierSpec; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multiset; import com.google.common.collect.Sets; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import javax.annotation.Nullable; /** * Validates complete specs. * * @see SpecDefaults */ final class SpecValidations { protected static final int INFO_ROW_MAX_LENGTH = 128; private static final ImmutableSet<String> METADATA_KEY_BLACKLIST = ImmutableSet.of( "status-config-url", "status-uptime-deadline", "status-variable-path", "startup-script", "startup-script-url"); private static final ImmutableSet<Integer> VALID_GPU_COUNTS = ImmutableSet.of(0, 1, 2, 4, 8); private static final Pattern TIER_NAME_REGEX = Pattern.compile("[a-z0-9]+"); private static final ImmutableSet<String> SUPPORTED_ACCELERATOR_TYPES = // LINT.IfChange(gpuTypes) ImmutableSet.of( "nvidia-tesla-k80", "nvidia-tesla-p100", "nvidia-tesla-v100", "nvidia-tesla-p100-vws", "nvidia-tesla-p4", "nvidia-tesla-p4-vws", "nvidia-tesla-t4", "nvidia-tesla-t4-vws", "nvidia-tesla-a100", "nvidia-a100-80gb", "nvidia-l4", "nvidia-l4-vws", "nvidia-h100-80gb"); // LINT.ThenChange() private static final int MAX_NICS = 8; /** Validates that a spec is complete and reasonable. */ public static void validate(SingleVmDeploymentPackageSpec input) { validateImages(input.getImagesList()); validateBootDisk(input.getBootDisk()); validateAdditionalDisks(input.getAdditionalDisksList()); validateMachineType(input.getMachineType()); validateNetworkInterfaces(input.getNetworkInterfaces()); validateSingleVmFirewallRules(input.getFirewallRulesList()); validateSingleVmPasswords(input.getPasswordsList()); if (input.hasAdminUrl()) { validateSingleVmInstanceUrl(input.getAdminUrl(), "Admin URL"); } if (input.hasSiteUrl()) { validateSingleVmInstanceUrl(input.getSiteUrl(), "Site URL"); } validateGcpAuthScopes(input.getGcpAuthScopesList()); if (input.hasGceStartupScript()) { validateStartupScript(input.getGceStartupScript()); } if (input.hasApplicationStatus()) { validateApplicationStatus(input.getApplicationStatus()); } if (input.hasPostDeploy()) { validateSingleVmPostDeployInfo(input.getPostDeploy()); } if (input.hasDeployInput()) { validateDeployInput(input.getDeployInput()); } if (input.hasStackdriver()) { validateStackdriver(input.getStackdriver()); } validateSingleVmGceMetadataItems(input.getGceMetadataItemsList()); validateMetadataKeyUniqueness(input); validateAccelerators(input.getAcceleratorsList()); } /** Validates that a spec is complete and reasonable. */ public static void validate(MultiVmDeploymentPackageSpec input) { checkArgument(input.getTiersCount() > 0, "At least one tier must be specified"); for (VmTierSpec tier : input.getTiersList()) { checkArgument( TIER_NAME_REGEX.matcher(tier.getName()).matches(), "Tier must have a valid lowercased name"); checkArgument(tier.getTitle().length() > 0, "Tier must have a valid title"); validateImages(tier.getImagesList()); validateBootDisk(tier.getBootDisk()); validateAdditionalDisks(tier.getAdditionalDisksList()); validateMachineType(tier.getMachineType()); validateNetworkInterfaces(tier.getNetworkInterfaces()); validateMultiVmFirewallRules(tier.getFirewallRulesList()); validateGcpAuthScopes(tier.getGcpAuthScopesList()); if (tier.hasGceStartupScript()) { validateStartupScript(tier.getGceStartupScript()); } if (tier.hasApplicationStatus()) { validateApplicationStatus(tier.getApplicationStatus()); } validateMultiVmGceMetadataItems(tier.getGceMetadataItemsList()); validateAccelerators(tier.getAcceleratorsList()); } validateMultiVmPasswords(input.getPasswordsList()); if (input.hasAdminUrl()) { validateMultiVmInstanceUrl(input.getAdminUrl(), "Admin URL"); } if (input.hasSiteUrl()) { validateMultiVmInstanceUrl(input.getSiteUrl(), "Site URL"); } if (input.hasPostDeploy()) { validateMultiVmPostDeployInfo(input.getPostDeploy()); } if (input.hasDeployInput()) { validateDeployInput(input.getDeployInput()); } if (input.hasStackdriver()) { validateStackdriver(input.getStackdriver()); } validateMetadataKeyUniqueness(input); } private static void validateCommonDiskOptions(DiskSpec input) { checkArgument(input.hasDiskSize(), "Disk size must be specified"); checkArgument( input.getDiskSize().getDefaultSizeGb() > 0, "A valid default disk size must be specified"); checkArgument(input.hasDiskType(), "Disk type must be specified"); checkArgument( !input.getDiskType().getDefaultType().isEmpty(), "A valid default disk type must be specified"); checkArgument( !input.getDisplayLabel().isEmpty(), "A valid disk display label must be specified"); } private static void validateBootDisk(DiskSpec input) { validateCommonDiskOptions(input); } private static void validateAdditionalDisks(List<DiskSpec> input) { for (DiskSpec disk : input) { validateCommonDiskOptions(disk); checkArgument(disk.hasDeviceNameSuffix(), "Disk must have a device name specified"); } } private static void validateSingleVmFirewallRules(List<FirewallRuleSpec> firewallRules) { validateCommonFirewallRules(firewallRules); for (FirewallRuleSpec rule : firewallRules) { checkArgument( rule.getAllowedSource() == TrafficSource.PUBLIC, "SingleVM only supports PUBLIC firewall rules"); } } private static void validateMultiVmFirewallRules(List<FirewallRuleSpec> firewallRules) { validateCommonFirewallRules(firewallRules); } private static void validateCommonFirewallRules(List<FirewallRuleSpec> firewallRules) { for (FirewallRuleSpec rule : firewallRules) { checkArgument( rule.getAllowedSource() != TrafficSource.SOURCE_UNSPECIFIED, "A firewall rule must have a valid source"); checkArgument( rule.getProtocol() != FirewallRuleSpec.Protocol.PROTOCOL_UNSPECIFIED, "A firewall rule must have a valid protocol"); checkArgument( !rule.getProtocol().equals(FirewallRuleSpec.Protocol.ICMP) || rule.getPort().isEmpty(), "ICMP firewall rule must not specify a port"); } } private static void validateGcpAuthScopes(List<GcpAuthScopeSpec> gcpAuthScopes) { Set<GcpAuthScopeSpec.Scope> seenScopes = new HashSet<>(); for (GcpAuthScopeSpec spec : gcpAuthScopes) { checkArgument( spec.getScope() != GcpAuthScopeSpec.Scope.SCOPE_UNSPECIFIED, "The GcpAuthScopeSpec must have a valid scope value"); checkArgument( seenScopes.add(spec.getScope()), "Duplicate scope in GcpAuthScopeSpec list: " + spec.getScope()); } } private static void validateImages(List<ImageSpec> imageSpecs) { checkArgument(imageSpecs.size() >= 1, "At least one image must be specified"); for (ImageSpec imageSpec : imageSpecs) { validateImage(imageSpec); } } private static void validateImage(ImageSpec imageSpec) { checkArgument(!imageSpec.getName().isEmpty(), "Image must have a valid name"); checkArgument(!imageSpec.getProject().isEmpty(), "Image must have a valid project"); } private static void validateSingleVmInstanceUrl(@Nullable InstanceUrlSpec spec, String urlName) { validateCommonInstanceUrl(spec, urlName); } private static void validateMultiVmInstanceUrl(@Nullable InstanceUrlSpec spec, String urlName) { if (spec == null) { return; } validateCommonInstanceUrl(spec, urlName); checkArgument(spec.hasTierVm(), "A tier VM is required for " + urlName); checkArgument( spec.getTierVm().getTier().length() > 0, "Tier VM must have a valid tier for " + urlName); } private static void validateCommonInstanceUrl(@Nullable InstanceUrlSpec spec, String urlName) { if (spec == null) { return; } checkArgument( spec.getScheme() != InstanceUrlSpec.Scheme.SCHEME_UNSPECIFIED, "A scheme is required for %s", urlName); } private static void validateSingleVmInstanceConnectSpec( ConnectToInstanceSpec spec, PostDeployInfo postDeployInfo) { validateCommonInstanceConnectSpec(spec, postDeployInfo); checkArgument(!spec.hasTierVm(), "Tier VM must not be specified for a single vm spec"); } private static void validateMultiVmInstanceConnectSpec( ConnectToInstanceSpec spec, PostDeployInfo postDeployInfo) { validateCommonInstanceConnectSpec(spec, postDeployInfo); checkArgument( spec.hasTierVm() && !Strings.isNullOrEmpty(spec.getTierVm().getTier()), "Multi-vm connect button spec must specify valid tier name"); } private static void validateCommonInstanceConnectSpec( ConnectToInstanceSpec spec, PostDeployInfo postDeployInfo) { // Keep support for deprecated attribute of connect_button_label. // If the Autogen spec has different values specified for both, the display_label of // the connect button and connect_button_label of post deploy info, it should fail. checkArgument( Strings.isNullOrEmpty(spec.getDisplayLabel()) || Strings.isNullOrEmpty(postDeployInfo.getConnectButtonLabel()) || spec.getDisplayLabel().equals(postDeployInfo.getConnectButtonLabel()), "At most only one of connect button's display_label and post deploy's " + "connect_button_label can be specified, or they must have the same value"); } private static void validateMachineType(MachineTypeSpec machineType) { checkArgument( !machineType.getDefaultMachineType().getGceMachineType().isEmpty(), "Default machine type name must be valid"); if (machineType.hasMinimum()) { // If unset, these values will be zero (which is allowed) checkArgument(machineType.getMinimum().getCpu() >= 0, "Minimum CPUs must be nonnegative"); checkArgument(machineType.getMinimum().getRamGb() >= 0, "Minimum RAM must be nonnegative"); } } private static void validateNetworkInterfaces(NetworkInterfacesSpec spec) { int min = spec.getMinCount(); int max = spec.getMaxCount(); checkArgument(min > 0, "Minimum number of Network interfaces must be greater than 0."); checkArgument( max >= min && max <= MAX_NICS, String.format( "Maxmium number of Network interfaces must be greater than minimum and at most %d.", MAX_NICS)); checkArgument( spec.getLabelsCount() <= spec.getMinCount() + 1, "The number of labels must not exceed min_count + 1."); validateExternalIp(spec.getExternalIp()); } @VisibleForTesting static void validateExternalIp(ExternalIpSpec externalIp) { checkArgument( externalIp.getDefaultType() != ExternalIpSpec.Type.TYPE_UNSPECIFIED, "External IP default type must have a valid type"); } @VisibleForTesting static void validateSingleVmPasswords(List<PasswordSpec> passwords) { for (PasswordSpec password : passwords) { validateCommonPassword(password); if (password.hasGenerateIf()) { validateSingleVmBooleanExpression(password.getGenerateIf()); } } } @VisibleForTesting static void validateMultiVmPasswords(List<PasswordSpec> passwords) { for (PasswordSpec password : passwords) { validateCommonPassword(password); if (password.hasGenerateIf()) { validateMultiVmBooleanExpression(password.getGenerateIf()); } } } private static void validateCommonPassword(PasswordSpec password) { checkArgument(!password.getMetadataKey().isEmpty(), "Password must have a valid metadata key"); checkArgument(password.getLength() > 0, "Password must have a valid length"); checkArgument( !password.getDisplayLabel().isEmpty(), "Password must have a valid display label"); checkArgument( !METADATA_KEY_BLACKLIST.contains(password.getMetadataKey()), "Password metadata key cannot be one of " + METADATA_KEY_BLACKLIST); } @VisibleForTesting static void validateApplicationStatus(ApplicationStatusSpec appStatus) { if (appStatus.getType() == ApplicationStatusSpec.StatusType.WAITER) { checkArgument( appStatus.hasWaiter(), "Application status of type WAITER must have a valid waiter spec"); checkArgument( appStatus.getWaiter().getWaiterTimeoutSecs() > 0, "Application status waiter must have a valid timeout"); if (appStatus.getWaiter().hasScript()) { checkArgument( appStatus.getWaiter().getScript().getCheckTimeoutSecs() > 0, "Application status waiter script must have a valid timeout"); } } else { checkArgument( !appStatus.hasWaiter(), "Waiter spec should only be set for an application status of type WAITER"); } } @VisibleForTesting static void validateSingleVmPostDeployInfo(PostDeployInfo postDeployInfo) { for (PostDeployInfo.ActionItem item : postDeployInfo.getActionItemsList()) { validateCommonActionItem(item); if (item.hasShowIf()) { validateSingleVmBooleanExpression(item.getShowIf()); } } for (PostDeployInfo.InfoRow row : postDeployInfo.getInfoRowsList()) { validateCommonInfoRow(row); if (row.hasShowIf()) { validateSingleVmBooleanExpression(row.getShowIf()); } } if (postDeployInfo.hasConnectButton()) { validateSingleVmInstanceConnectSpec(postDeployInfo.getConnectButton(), postDeployInfo); } } @VisibleForTesting static void validateMultiVmPostDeployInfo(PostDeployInfo postDeployInfo) { for (PostDeployInfo.ActionItem item : postDeployInfo.getActionItemsList()) { validateCommonActionItem(item); if (item.hasShowIf()) { validateMultiVmBooleanExpression(item.getShowIf()); } } for (PostDeployInfo.InfoRow row : postDeployInfo.getInfoRowsList()) { validateCommonInfoRow(row); if (row.hasShowIf()) { validateMultiVmBooleanExpression(row.getShowIf()); } } if (postDeployInfo.hasConnectButton()) { validateMultiVmInstanceConnectSpec(postDeployInfo.getConnectButton(), postDeployInfo); } } private static void validateCommonActionItem(PostDeployInfo.ActionItem item) { checkArgument( !item.getHeading().isEmpty(), "Post deploy action item must have a valid heading"); checkArgument( !item.getDescription().isEmpty() || !item.getSnippet().isEmpty(), "Post deploy action item must have at least description or snippet"); } private static void validateCommonInfoRow(PostDeployInfo.InfoRow row) { switch (row.getValueSpecCase()) { case VALUE: checkArgument( row.getValue().length() <= INFO_ROW_MAX_LENGTH, String.format( "Info row value length cannot exceed the limit of %d characters", INFO_ROW_MAX_LENGTH)); break; case VALUE_FROM_DEPLOY_INPUT_FIELD: checkArgument( !Strings.isNullOrEmpty(row.getValueFromDeployInputField()), "Expected non-empty deploy input field name for info row value"); break; default: throw new IllegalArgumentException("Unexpected info row value"); } } @VisibleForTesting static void validateSingleVmBooleanExpression(BooleanExpression spec) { validateCommonBooleanExpression(spec); if (spec.hasHasExternalIp()) { checkArgument( Strings.isNullOrEmpty(spec.getHasExternalIp().getTier()), "Tier attribute must not be specified for has_external_ip boolean " + "expression in a single VM's spec"); } } @VisibleForTesting static void validateMultiVmBooleanExpression(BooleanExpression spec) { validateCommonBooleanExpression(spec); if (spec.hasHasExternalIp()) { checkArgument( !Strings.isNullOrEmpty(spec.getHasExternalIp().getTier()), "Tier attribute must be specified for has_external_ip boolean expression " + "in a multi VM's spec"); } } private static void validateCommonBooleanExpression(BooleanExpression spec) { if (spec.hasBooleanDeployInputField()) { BooleanDeployInputField field = spec.getBooleanDeployInputField(); checkArgument( !field.getName().isEmpty(), "Boolean expression for deploy input field must specify the field's name"); } } @VisibleForTesting static void validateSingleVmGceMetadataItems(List<GceMetadataItem> items) { for (GceMetadataItem item : items) { checkArgument(!item.getKey().isEmpty(), "GCE metadata item must have a valid key"); checkArgument( !METADATA_KEY_BLACKLIST.contains(item.getKey()), "GCE metadata item key cannot be one of " + METADATA_KEY_BLACKLIST); checkArgument( item.getValueSpecCase() != ValueSpecCase.VALUESPEC_NOT_SET, "GCE metadata item must have a valid value"); checkArgument( item.getValueSpecCase() != ValueSpecCase.TIER_VM_NAMES, "GCE metadata item for single VM cannot specify tier VM"); } } @VisibleForTesting static void validateMultiVmGceMetadataItems(List<GceMetadataItem> items) { for (GceMetadataItem item : items) { checkArgument(!item.getKey().isEmpty(), "GCE metadata item must have a valid key"); checkArgument( !METADATA_KEY_BLACKLIST.contains(item.getKey()), "GCE metadata item key cannot be one of " + METADATA_KEY_BLACKLIST); checkArgument( item.getValueSpecCase() != ValueSpecCase.VALUESPEC_NOT_SET, "GCE metadata item must have a valid value"); } } private static void validateMetadataKeyUniqueness(SingleVmDeploymentPackageSpec spec) { // Ensures that metadata keys are unique. Multiset<String> metadataKeyCounts = HashMultiset.create(); for (PasswordSpec password : spec.getPasswordsList()) { metadataKeyCounts.add(password.getMetadataKey()); } for (GceMetadataItem metadataItem : spec.getGceMetadataItemsList()) { metadataKeyCounts.add(metadataItem.getKey()); } for (Multiset.Entry<String> entry : metadataKeyCounts.entrySet()) { if (entry.getCount() > 1) { throw new IllegalArgumentException( String.format("Metadata key '%s' is not unique", entry.getElement())); } } } private static void validateMetadataKeyUniqueness(MultiVmDeploymentPackageSpec spec) { // Ensures that metadata keys are unique. Multiset<String> metadataKeyCounts = HashMultiset.create(); for (PasswordSpec password : spec.getPasswordsList()) { metadataKeyCounts.add(password.getMetadataKey()); } for (VmTierSpec tier : spec.getTiersList()) { Multiset<String> perTier = HashMultiset.create(metadataKeyCounts); for (GceMetadataItem metadataItem : tier.getGceMetadataItemsList()) { perTier.add(metadataItem.getKey()); } for (Multiset.Entry<String> entry : perTier.entrySet()) { if (entry.getCount() > 1) { throw new IllegalArgumentException( String.format("Metadata key '%s' is not unique", entry.getElement())); } } } } @VisibleForTesting static void validateAccelerators(List<AcceleratorSpec> accelerators) { if (accelerators.isEmpty()) { return; } checkArgument(accelerators.size() <= 1, "At most one accelerator allowed."); AcceleratorSpec accelerator = accelerators.get(0); checkArgument( accelerator.getTypesList().size() >= 1, "Accelerators must have at least one type."); Set<String> gpuTypes = ImmutableSet.copyOf(accelerator.getTypesList()); Set<String> unsupportedTypes = Sets.difference(gpuTypes, SUPPORTED_ACCELERATOR_TYPES); checkArgument( unsupportedTypes.isEmpty(), "Unsupported accelerator types: %s", unsupportedTypes); checkArgument(accelerator.getMinCount() >= 0, "Accelerator min count must not be negative."); checkArgument( VALID_GPU_COUNTS.contains(accelerator.getMinCount()), "Accelerator min count must be one of: %s", VALID_GPU_COUNTS.toString()); if (accelerator.getMaxCount() != 0) { checkArgument(accelerator.getMaxCount() > 0, "Accelerator max count must be greater than 0."); checkArgument( accelerator.getMinCount() <= accelerator.getMaxCount(), "Accelerator min count must not be greater than max count."); checkArgument( VALID_GPU_COUNTS.contains(accelerator.getMaxCount()), "Accelerator max count must be one of: %s", VALID_GPU_COUNTS.toString()); } if (accelerator.getDefaultCount() != 0) { checkArgument( accelerator.getDefaultCount() >= 0, "Accelerator default count must not be negative."); checkArgument( accelerator.getDefaultCount() >= accelerator.getMinCount(), "Accelerator default count must not be less than min count."); if (accelerator.getMaxCount() != 0) { checkArgument( accelerator.getDefaultCount() <= accelerator.getMaxCount(), "Accelerator default count must not be greater than max count."); } checkArgument( VALID_GPU_COUNTS.contains(accelerator.getDefaultCount()), "Accelerator default count must be one of: %s", VALID_GPU_COUNTS.toString()); } if (!accelerator.getDefaultType().isEmpty()) { checkArgument( gpuTypes.contains(accelerator.getDefaultType()), "Default Accelerator Type must be one of %s", accelerator.getTypesList()); } } @VisibleForTesting static void validateDeployInput(DeployInputSpec spec) { Set<String> runningSectionNames = new HashSet<>(); Set<String> runningFieldNames = new HashSet<>(); Set<String> runningDisplayGroups = new HashSet<>(); for (DeployInputSection section : spec.getSectionsList()) { checkArgument( section.getPlacement() != Placement.PLACEMENT_UNSPECIFIED, "Deploy input section must have a valid placement"); switch (section.getPlacement()) { case MAIN: break; case TIER: checkArgument( section.getTier().length() > 0, "Tier input section must have a valid tier name"); break; default: checkArgument( !section.getName().isEmpty(), "Custom deploy input section must have valid name"); if (!runningSectionNames.add(section.getName())) { throw new IllegalArgumentException( "Deploy input sections with the same name: " + section.getName()); } checkArgument( !section.getTitle().isEmpty(), "Custom deploy input section must have a valid title"); } checkArgument( section.getFieldsCount() > 0, "Deploy input section must have at least 1 field"); validateDeployInputFields(section.getFieldsList(), runningFieldNames, runningDisplayGroups); } } @VisibleForTesting static void validateStartupScript(GceStartupScriptSpec startupScript) { checkArgument( !Strings.isNullOrEmpty(startupScript.getBashScriptContent()), "Startup script must specify non-empty script content"); } @VisibleForTesting static void validateDeployInputFields( List<DeployInputField> fields, Set<String> runningFieldNames, Set<String> runningDisplayGroups) { boolean isInBooleanGroup = false; for (DeployInputField field : fields) { checkArgument(!field.getName().isEmpty(), "Deploy input field must have a valid name"); if (!runningFieldNames.add(field.getName())) { throw new IllegalArgumentException( "Deploy input fields with the same name: " + field.getName()); } checkArgument(!field.getTitle().isEmpty(), "Deploy input field must have a valid title"); switch (field.getTypeCase()) { case BOOLEAN_CHECKBOX: break; case GROUPED_BOOLEAN_CHECKBOX: { GroupedBooleanCheckbox checkbox = field.getGroupedBooleanCheckbox(); if (!isInBooleanGroup) { checkArgument( checkbox.hasDisplayGroup(), String.format( "The first grouped boolean checkbox '%s' must have a display group", field.getName())); } if (checkbox.hasDisplayGroup()) { GroupedBooleanCheckbox.DisplayGroup displayGroup = checkbox.getDisplayGroup(); checkArgument( !displayGroup.getName().isEmpty(), String.format("Field '%s' has a display group without a name", field.getName())); if (!runningDisplayGroups.add(displayGroup.getName())) { throw new IllegalArgumentException( "Display groups with the same name: " + displayGroup.getName()); } checkArgument( !displayGroup.getTitle().isEmpty(), String.format("Display group '%s' must have a title", displayGroup.getName())); } break; } case INTEGER_BOX: { if (field.getRequired()) { IntegerBox box = field.getIntegerBox(); checkArgument( box.hasDefaultValue() || box.hasTestDefaultValue(), String.format( "Field '%s' is required - it should have defaultValue or testDefaultValue", field.getName())); } break; } case INTEGER_DROPDOWN: { IntegerDropdown dropdown = field.getIntegerDropdown(); checkArgument( dropdown.getValuesCount() > 0, String.format("Field '%s' must have at least 1 value", field.getName())); if (dropdown.hasDefaultValueIndex()) { checkArgument( dropdown.getDefaultValueIndex().getValue() < dropdown.getValuesCount(), String.format( "Invalid default index %d while there are only %d values in field '%s'", dropdown.getDefaultValueIndex().getValue(), dropdown.getValuesCount(), field.getName())); } break; } case STRING_BOX: { if (field.getRequired()) { StringBox box = field.getStringBox(); boolean hasDefaultValue = !Strings.isNullOrEmpty(box.getDefaultValue()); boolean hasTestDefaultValue = !Strings.isNullOrEmpty(box.getTestDefaultValue()); checkArgument( hasDefaultValue || hasTestDefaultValue, String.format( "Field '%s' is required - it should have defaultValue or testDefaultValue", field.getName())); } break; } case STRING_DROPDOWN: { StringDropdown dropdown = field.getStringDropdown(); checkArgument( dropdown.getValuesCount() > 0, String.format("Field '%s' must have at least 1 value", field.getName())); if (dropdown.hasDefaultValueIndex()) { checkArgument( dropdown.getDefaultValueIndex().getValue() < dropdown.getValuesCount(), String.format( "Invalid default index %d while there are only %d values in field '%s'", dropdown.getDefaultValueIndex().getValue(), dropdown.getValuesCount(), field.getName())); } break; } case ZONE_DROPDOWN: break; case EMAIL_BOX: if (field.getRequired()) { EmailBox box = field.getEmailBox(); // EmailBox should always have a testDefaultValue. // It's either being specified by defaultValue, // explicitly by testDefaultValue, or being filled in by defaults. boolean hasTestDefaultValue = !box.getTestDefaultValue().isEmpty(); checkArgument( hasTestDefaultValue, "Field '%s' is required - it should have testDefaultValue", field.getName()); } break; case TYPE_NOT_SET: throw new IllegalArgumentException("Deploy input field must be of exactly one type"); } isInBooleanGroup = field.getTypeCase() == TypeCase.GROUPED_BOOLEAN_CHECKBOX; } } @VisibleForTesting static void validateStackdriver(StackdriverSpec stackdriver) { checkArgument( stackdriver.hasMonitoring() || stackdriver.hasLogging(), "Invalid Stackdriver spec. At least one of logging or monitoring must be specified"); } private SpecValidations() {} }