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
|
// 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) {
if (currentCluster.getSearch().getSearchCluster() != null && nextCluster.getSearch().getSearchCluster() != null) {
validateStreamingCluster(currentCluster, currentCluster.getSearch().getSearchCluster(),
nextCluster, nextCluster.getSearch().getSearchCluster())
.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());
}
}
|