/** * Whether or not the nodes requested can share physical host with other applications. * A security feature which only makes sense for prod. */ public boolean decideExclusivity(boolean requestedExclusivity) { return requestedExclusivity && zone.environment() == Environment.prod; }
/** * Throw if the node count is 1 for container and content clusters and we're in a production zone * * @return the argument node count * @throws IllegalArgumentException if only one node is requested and we can fail */ private int ensureRedundancy(int nodeCount, ClusterSpec.Type clusterType, boolean canFail) { if (canFail && nodeCount == 1 && Arrays.asList(ClusterSpec.Type.container, ClusterSpec.Type.content).contains(clusterType) && zone.environment().isProduction()) throw new IllegalArgumentException("Deployments to prod require at least 2 nodes per cluster for redundancy"); return nodeCount; }
private void preprocessXML(File destination, File inputXml, Zone zone) throws ParserConfigurationException, TransformerException, SAXException, IOException { Document document = new XmlPreProcessor(appDir, inputXml, zone.environment(), zone.region()).run(); Transformer transformer = TransformerFactory.newInstance().newTransformer(); try (FileOutputStream outputStream = new FileOutputStream(destination)) { transformer.transform(new DOMSource(document), new StreamResult(outputStream)); } }
/** Returns whether the current node fail count should be used as an indicator of hardware issue */ private boolean failCountIndicatesHardwareIssue(Node node) { if (node.flavor().getType() == Flavor.Type.DOCKER_CONTAINER) return false; return (zone.environment() == Environment.prod || zone.environment() == Environment.staging) && node.status().failCount() >= maxAllowedFailures; }
public int decideSize(Capacity requestedCapacity, ClusterSpec.Type clusterType) { int requestedNodes = ensureRedundancy(requestedCapacity.nodeCount(), clusterType, requestedCapacity.canFail()); if (requestedCapacity.isRequired()) return requestedNodes; switch(zone.environment()) { case dev : case test : return 1; case perf : return Math.min(requestedCapacity.nodeCount(), 3); case staging: return requestedNodes <= 1 ? requestedNodes : Math.max(2, requestedNodes / 10); case prod : return requestedNodes; default : throw new IllegalArgumentException("Unsupported environment " + zone.environment()); } }
public FailedExpirer(NodeRepository nodeRepository, Zone zone, Clock clock, Duration interval, JobControl jobControl) { super(nodeRepository, interval, jobControl); this.nodeRepository = nodeRepository; this.zone = zone; this.clock = clock; if (zone.system() == SystemName.main) { if (zone.environment() == Environment.staging || zone.environment() == Environment.test) { defaultExpiry = Duration.ofHours(1); } else { defaultExpiry = Duration.ofDays(4); } containerExpiry = Duration.ofHours(1); } else { defaultExpiry = containerExpiry = Duration.ofMinutes(30); } }
private boolean zoneHasActiveRotation(Zone zone, DeploymentSpec spec) { return spec.zones().stream() .anyMatch(declaredZone -> declaredZone.deploysTo(zone.environment(), Optional.of(zone.region())) && declaredZone.active()); }
private String getConfigserverIdentityName() { return String.format("%s.provider_%s_%s", zone.system() == SystemName.main ? "vespa.vespa" : "vespa.vespa.cd", zone.environment().value(), zone.region().value()); } }
@Override public boolean isActive() { if(zone.system() == SystemName.cd) { return zone.environment() == Environment.dev || zone.environment() == Environment.prod; } if (zone.system() == SystemName.main) { if (zone.region().equals(RegionName.from("us-east-3"))) { return zone.environment() == Environment.perf || zone.environment() == Environment.prod; } else if (zone.region().equals(RegionName.from("us-west-1"))) { return zone.environment() == Environment.prod; } else if (zone.region().equals(RegionName.from("us-central-1"))) { return zone.environment() == Environment.prod; } else if (zone.region().equals(RegionName.from("ap-southeast-1"))) { return zone.environment() == Environment.prod; } else if (zone.region().equals(RegionName.from("ap-northeast-1"))) { return zone.environment() == Environment.prod; } else if (zone.region().equals(RegionName.from("ap-northeast-2"))) { return zone.environment() == Environment.prod; } else if (zone.region().equals(RegionName.from("eu-west-1"))) { return zone.environment() == Environment.prod; } } return false; }
@Inject public NodeRepositoryProvisioner(NodeRepository nodeRepository, NodeFlavors flavors, Zone zone, ProvisionServiceProvider provisionServiceProvider) { this.nodeRepository = nodeRepository; this.capacityPolicies = new CapacityPolicies(zone, flavors); this.zone = zone; this.preparer = new Preparer(nodeRepository, zone.environment().equals(Environment.prod) ? SPARE_CAPACITY_PROD : SPARE_CAPACITY_NONPROD); this.activator = new Activator(nodeRepository); this.loadBalancerProvisioner = provisionServiceProvider.getLoadBalancerService().map(lbService -> new LoadBalancerProvisioner(nodeRepository, lbService)); }
public Flavor decideFlavor(Capacity requestedCapacity, ClusterSpec cluster) { // for now, always use the requested flavor if a docker flavor is requested Optional<String> requestedFlavor = requestedCapacity.flavor(); if (requestedFlavor.isPresent() && flavors.getFlavorOrThrow(requestedFlavor.get()).getType() == Flavor.Type.DOCKER_CONTAINER) return flavors.getFlavorOrThrow(requestedFlavor.get()); String defaultFlavorName = zone.defaultFlavor(cluster.type()); if (zone.system() == SystemName.cd) return flavors.getFlavorOrThrow(requestedFlavor.orElse(defaultFlavorName)); switch(zone.environment()) { case dev : case test : case staging : return flavors.getFlavorOrThrow(defaultFlavorName); default : return flavors.getFlavorOrThrow(requestedFlavor.orElse(defaultFlavorName)); } }
/** Returns whether to reboot node as part of transition to given state. This is done to get rid of any lingering * unwanted state (e.g. processes) on non-host nodes. */ private boolean rebootOnTransitionTo(Node.State state, Node node) { if (node.type().isDockerHost()) return false; // Reboot of host nodes is handled by NodeRebooter if (zone.environment().isTest()) return false; // We want to reuse nodes quickly in test environments return node.state() != Node.State.dirty && state == Node.State.dirty; }
private boolean checkForClashingParentHost() { return nodeRepository.zone().system() == SystemName.main && nodeRepository.zone().environment().isProduction(); }
@Override public void validate(VespaModel model, DeployState deployState) { if (! deployState.isHosted()) return; if (! deployState.zone().environment().isProduction()) return; if (model.getAdmin().getApplicationType() != ApplicationType.DEFAULT) return; List<String> offendingClusters = new ArrayList<>(); for (ContainerCluster cluster : model.getContainerClusters().values()) { if (cluster.getHttp() == null || ! cluster.getHttp().getAccessControl().isPresent() || ! cluster.getHttp().getAccessControl().get().writeEnabled) if (hasHandlerThatNeedsProtection(cluster) || ! cluster.getAllServlets().isEmpty()) offendingClusters.add(cluster.getName()); } if (! offendingClusters.isEmpty()) throw new IllegalArgumentException( "Access-control must be enabled for write operations to container clusters in production zones: " + mkString(offendingClusters, "[", ", ", "].")); }
private void addIdentityProvider(ContainerCluster cluster, List<ConfigServerSpec> configServerSpecs, HostName loadBalancerName, URI ztsUrl, String athenzDnsSuffix, Zone zone, DeploymentSpec spec) { spec.athenzDomain().ifPresent(domain -> { AthenzService service = spec.athenzService(zone.environment(), zone.region()) .orElseThrow(() -> new RuntimeException("Missing Athenz service configuration")); String zoneDnsSuffix = zone.environment().value() + "-" + zone.region().value() + "." + athenzDnsSuffix; IdentityProvider identityProvider = new IdentityProvider(domain, service, getLoadBalancerName(loadBalancerName, configServerSpecs), ztsUrl, zoneDnsSuffix, zone); cluster.addComponent(identityProvider); cluster.getContainers().forEach(container -> { container.setProp("identity.domain", domain.value()); container.setProp("identity.service", service.value()); }); }); }
/** * Returns the distribution bits this cluster should use. * On Hosted Vespa this is hardcoded and not computed from the nodes because reducing the number of nodes is a common * operation, while reducing the number of distribution bits can lead to consistency problems. * This hardcoded value should work fine from 1-200 nodes. Those who have more will need to set this value * in config and not remove it again if they reduce the node count. */ public int distributionBits() { if (zone.environment() == Environment.prod && ! zone.equals(Zone.defaultZone())) { return 16; } else { // hosted test zone, or self-hosted system // hosted test zones: have few nodes and use visiting in tests: This is slow with 16 bits (to many buckets) // self hosted systems: should probably default to 16 bits, but the transition may cause problems return DistributionBitCalculator.getDistributionBits(getNodeCountPerGroup(), getDistributionMode()); } }
private String getHostFromVespaCertificate(List<SubjectAlternativeName> sans) { // TODO Remove this branch once all BM nodes are gone if (sans.stream().anyMatch(san -> san.getValue().endsWith("ostk.yahoo.cloud"))) { return getHostFromCalypsoCertificate(sans); } VespaUniqueInstanceId instanceId = VespaUniqueInstanceId.fromDottedString(getUniqueInstanceId(sans)); if (!zone.environment().value().equals(instanceId.environment())) throw new NodeIdentifierException("Invalid environment: " + instanceId.environment()); if (!zone.region().value().equals(instanceId.region())) throw new NodeIdentifierException("Invalid region(): " + instanceId.region()); List<Node> applicationNodes = nodeRepository.getNodes(ApplicationId.from(instanceId.tenant(), instanceId.application(), instanceId.instance())); return applicationNodes.stream() .filter( node -> node.allocation() .map(allocation -> allocation.membership().index() == instanceId.clusterIndex() && allocation.membership().cluster().id().value().equals(instanceId.clusterId())) .orElse(false)) .map(Node::hostname) .findFirst() .orElseThrow(() -> new NodeIdentifierException("Could not find any node with instance id: " + instanceId.asDottedString())); }
/** * Returns a config server config containing the right zone settings (and defaults for the rest). * This is useful to allow applications to find out in which zone they are runnung by having the Zone * object (which is constructed from this config) injected. */ @Override public void getConfig(ConfigserverConfig.Builder builder) { builder.system(zone.system().name()); builder.environment(zone.environment().value()); builder.region(zone.region().value()); }
private SentinelConfig.Application.Builder getApplicationConfig() { SentinelConfig.Application.Builder builder = new SentinelConfig.Application.Builder(); builder.tenant(applicationId.tenant().value()); builder.name(applicationId.application().value()); builder.environment(zone.environment().value()); builder.region(zone.region().value()); builder.instance(applicationId.instance().value()); return builder; }
private void addClusterContent(ContainerCluster cluster, Element spec, ConfigModelContext context) { DeployState deployState = context.getDeployState(); DocumentFactoryBuilder.buildDocumentFactories(cluster, spec); addConfiguredComponents(deployState, cluster, spec); addSecretStore(cluster, spec); addHandlers(deployState, cluster, spec); addRestApis(deployState, spec, cluster); addServlets(deployState, spec, cluster); addProcessing(deployState, spec, cluster); addSearch(deployState, spec, cluster); addModelEvaluation(spec, cluster, context); addDocproc(deployState, spec, cluster); addDocumentApi(spec, cluster); // NOTE: Must be done after addSearch addDefaultHandlers(cluster); addStatusHandlers(cluster, context); addHttp(deployState, spec, cluster); addAccessLogs(deployState, cluster, spec); addRoutingAliases(cluster, spec, deployState.zone().environment()); addNodes(cluster, spec, context); addClientProviders(deployState, spec, cluster); addServerProviders(deployState, spec, cluster); addAthensCopperArgos(cluster, context); // Must be added after nodes. }