aboutsummaryrefslogtreecommitdiffstats
path: root/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/StreamingSearchClusterChangeValidator.java
blob: 0d42219dade9680be1d8b3bb47c6482a88dcfb9d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.application.validation.change;

import com.yahoo.config.model.api.ServiceInfo;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.documentmodel.NewDocumentType;
import com.yahoo.schema.derived.AttributeFields;
import com.yahoo.schema.document.Attribute;
import com.yahoo.vespa.model.AbstractService;
import com.yahoo.vespa.model.application.validation.Validation.ChangeContext;
import com.yahoo.vespa.model.application.validation.change.search.ChangeMessageBuilder;
import com.yahoo.vespa.model.application.validation.change.search.DocumentTypeChangeValidator;
import com.yahoo.vespa.model.content.cluster.ContentCluster;
import com.yahoo.vespa.model.search.DocumentDatabase;
import com.yahoo.vespa.model.search.SearchCluster;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Validates the changes between all current and next streaming search clusters in a Vespa model.
 *
 * @author geirst
 */
public class StreamingSearchClusterChangeValidator implements ChangeValidator {

    @Override
    public void validate(ChangeContext context) {
        context.previousModel().getContentClusters().forEach((clusterName, currentCluster) -> {
            ContentCluster nextCluster = context.model().getContentClusters().get(clusterName);
            if (nextCluster != null) {
                var nextStreamingClusters = nextCluster.getSearch().getClusters();
                currentCluster.getSearch().getClusters().values().forEach(currentStreamingCluster -> {
                    SearchCluster nextStreamingCluster = nextStreamingClusters.get(currentStreamingCluster.getClusterName());
                    if (nextStreamingCluster != null) {
                        validateStreamingCluster(currentCluster, currentStreamingCluster, nextCluster, nextStreamingCluster).forEach(context::require);
                    }
                });
            }
        });
    }

    private static List<VespaConfigChangeAction> validateStreamingCluster(ContentCluster currentCluster,
                                                                     SearchCluster currentStreamingCluster,
                                                                     ContentCluster nextCluster,
                                                                     SearchCluster nextStreamingCluster) {
        List<VespaConfigChangeAction> result = new ArrayList<>();

        for (DocumentDatabase currentDB : currentStreamingCluster.getDocumentDbs()) {
            DocumentDatabase nextDB = nextStreamingCluster.getDocumentDB(currentDB.getName());
            if (nextDB != null) {
                result.addAll(validateDocumentDB(currentCluster, currentDB, nextCluster, nextDB));
            }
        }
        return result;
    }

    private static List<VespaConfigChangeAction> validateDocumentDB(ContentCluster currentCluster, DocumentDatabase currentDB,
                                                               ContentCluster nextCluster, DocumentDatabase nextDB) {
        List<VespaConfigChangeAction> result = new ArrayList<>();

        result.addAll(validateDocumentTypeChanges(currentCluster.id(),
                getDocumentType(currentCluster, currentDB),
                getDocumentType(nextCluster, nextDB)));
        result.addAll(validateAttributeFastAccessAdded(currentCluster.id(),
                currentDB.getDerivedConfiguration().getAttributeFields(),
                nextDB.getDerivedConfiguration().getAttributeFields()));
        result.addAll(validateAttributeFastAccessRemoved(currentCluster.id(),
                currentDB.getDerivedConfiguration().getAttributeFields(),
                nextDB.getDerivedConfiguration().getAttributeFields()));

        return modifyActions(result, getSearchNodeServices(nextCluster), nextDB.getName());
    }

    private static List<VespaConfigChangeAction> validateDocumentTypeChanges(ClusterSpec.Id id,
                                                                             NewDocumentType currentDocType,
                                                                             NewDocumentType nextDocType) {
        return new DocumentTypeChangeValidator(id, currentDocType, nextDocType).validate();
    }

    private static NewDocumentType getDocumentType(ContentCluster cluster, DocumentDatabase db) {
        return cluster.getDocumentDefinitions().get(db.getName());
    }

    private static List<VespaConfigChangeAction> validateAttributeFastAccessAdded(ClusterSpec.Id id,
                                                                                  AttributeFields currentAttributes,
                                                                                  AttributeFields nextAttributes) {
        return validateAttributeFastAccessChanged(id, nextAttributes, currentAttributes, "add");
    }

    private static List<VespaConfigChangeAction> validateAttributeFastAccessRemoved(ClusterSpec.Id id,
                                                                                    AttributeFields currentAttributes,
                                                                                    AttributeFields nextAttributes) {
        return validateAttributeFastAccessChanged(id, currentAttributes, nextAttributes, "remove");
    }

    private static List<VespaConfigChangeAction> validateAttributeFastAccessChanged(ClusterSpec.Id id,
                                                                                    AttributeFields lhsAttributes,
                                                                                    AttributeFields rhsAttributes,
                                                                                    String change) {
        return lhsAttributes.attributes().stream()
                .filter(attr -> attr.isFastAccess() &&
                        !hasFastAccessAttribute(attr.getName(), rhsAttributes))
                .map(attr -> new VespaRestartAction(id, new ChangeMessageBuilder(attr.getName()).addChange(change + " fast-access attribute").build()))
                .collect(Collectors.toList());
    }

    private static boolean hasFastAccessAttribute(String attrName, AttributeFields attributes) {
        Attribute attr = attributes.getAttribute(attrName);
        return (attr != null && attr.isFastAccess());
    }

    private static List<ServiceInfo> getSearchNodeServices(ContentCluster cluster) {
        return cluster.getSearch().getSearchNodes().stream()
                .map(AbstractService::getServiceInfo)
                .toList();
    }

    private static List<VespaConfigChangeAction> modifyActions(List<VespaConfigChangeAction> result,
                                                          List<ServiceInfo> services,
                                                          String docTypeName) {
        return result.stream()
                .map(action -> action.modifyAction("Document type '" + docTypeName + "': " + action.getMessage(),
                                                   services, docTypeName))
                .collect(Collectors.toList());
    }

}