2020import static com .linecorp .centraldogma .xds .internal .ControlPlaneService .CLUSTERS_DIRECTORY ;
2121import static com .linecorp .centraldogma .xds .internal .XdsResourceManager .LEGACY_RESOURCE_ID_PATTERN_STRING ;
2222import static com .linecorp .centraldogma .xds .internal .XdsResourceManager .RESOURCE_ID_PATTERN ;
23- import static com .linecorp .centraldogma .xds .internal .XdsResourceManager .removePrefix ;
2423
25- import java .util .regex .Matcher ;
24+ import java .io .IOException ;
25+ import java .util .concurrent .CompletableFuture ;
2626import java .util .regex .Pattern ;
2727
28- import com . google . protobuf . Empty ;
28+ import org . jspecify . annotations . Nullable ;
2929
30- import com .linecorp .centraldogma .xds .cluster .v1 .XdsClusterServiceGrpc .XdsClusterServiceImplBase ;
30+ import com .linecorp .armeria .common .HttpResponse ;
31+ import com .linecorp .armeria .common .HttpStatus ;
32+ import com .linecorp .armeria .server .annotation .Consumes ;
33+ import com .linecorp .armeria .server .annotation .Delete ;
34+ import com .linecorp .armeria .server .annotation .Param ;
35+ import com .linecorp .armeria .server .annotation .Post ;
36+ import com .linecorp .armeria .server .annotation .Put ;
37+ import com .linecorp .centraldogma .common .RepositoryRole ;
38+ import com .linecorp .centraldogma .xds .internal .RequiresXdsGroupRole ;
3139import com .linecorp .centraldogma .xds .internal .XdsResourceManager ;
3240
3341import io .envoyproxy .envoy .config .cluster .v3 .Cluster ;
34- import io .grpc .Status ;
35- import io .grpc .stub .StreamObserver ;
3642
3743/**
38- * Service for managing clusters.
44+ * Annotated service object for managing clusters.
3945 */
40- public final class XdsClusterService extends XdsClusterServiceImplBase {
46+ public final class XdsClusterService {
4147
4248 private static final Pattern CLUSTER_NAME_PATTERN =
4349 Pattern .compile ("^groups/([^/]+)/clusters/" + LEGACY_RESOURCE_ID_PATTERN_STRING + '$' );
@@ -51,69 +57,89 @@ public XdsClusterService(XdsResourceManager xdsResourceManager) {
5157 this .xdsResourceManager = xdsResourceManager ;
5258 }
5359
54- @ Override
55- public void createCluster (CreateClusterRequest request , StreamObserver <Cluster > responseObserver ) {
56- final String parent = request .getParent ();
57- final String group = removePrefix ("groups/" , parent );
58- xdsResourceManager .checkWritePermission (group );
59-
60- final String clusterId = request .getClusterId ();
60+ /**
61+ * POST /xds/groups/{group}/clusters
62+ *
63+ * <p>Creates a new cluster.
64+ */
65+ @ Post ("/xds/groups/{group}/clusters" )
66+ @ Consumes ("application/yaml" )
67+ @ RequiresXdsGroupRole (RepositoryRole .WRITE )
68+ public CompletableFuture <HttpResponse > createCluster (
69+ @ Param ("group" ) String group ,
70+ @ Param ("cluster_id" ) String clusterId ,
71+ @ Param ("summary" ) @ Nullable String summary ,
72+ String body ) {
6173 if (!RESOURCE_ID_PATTERN .matcher (clusterId ).matches ()) {
62- throw Status . INVALID_ARGUMENT . withDescription ( "Invalid cluster_id: " + clusterId +
63- " (expected: " + RESOURCE_ID_PATTERN + ')' )
64- . asRuntimeException ( );
74+ return CompletableFuture . completedFuture (
75+ XdsResourceManager . errorResponse ( HttpStatus . BAD_REQUEST ,
76+ "Invalid cluster ID: " + clusterId ) );
6577 }
66-
67- final String clusterName = parent + CLUSTERS_DIRECTORY + clusterId ;
68- final Cluster cluster
69- = request .getCluster ()
70- .toBuilder ()
71- // Ignore the specified name in the cluster and set the name with the format of
72- // "groups/{group}/clusters/{cluster}".
73- // https://github.com/aip-dev/google.aip.dev/blob/master/aip/general/0133.md#user-specified-ids
74- .setName (clusterName )
75- // Respect the DNS TTL would be more efficient in terms of DNS resolution.
76- // https://github.com/envoyproxy/envoy/issues/6876
77- // `respect_dns_ttl` is a `bool` field so it is not possible to check whether a value
78- // has not been set for the field. Until we create our own proto file, the value only
79- // can be set to false via the update API.
80- .setRespectDnsTtl (true )
81- .build ();
82- final String createSummary = isNullOrEmpty (request .getSummary ()) ?
83- "Create cluster: " + clusterName : request .getSummary ();
84- xdsResourceManager .push (responseObserver , group , clusterName , CLUSTERS_DIRECTORY + clusterId + ".yaml" ,
85- createSummary , cluster , currentAuthor (), true );
86- }
87-
88- @ Override
89- public void updateCluster (UpdateClusterRequest request , StreamObserver <Cluster > responseObserver ) {
90- final Cluster cluster = request .getCluster ();
91- final String clusterName = cluster .getName ();
92- final String group = checkClusterName (clusterName ).group (1 );
93- xdsResourceManager .checkWritePermission (group );
94- final String updateSummary = isNullOrEmpty (request .getSummary ()) ?
95- "Update cluster: " + clusterName : request .getSummary ();
96- xdsResourceManager .update (responseObserver , group , clusterName ,
97- updateSummary , cluster , currentAuthor ());
78+ final String clusterName = "groups/" + group + CLUSTERS_DIRECTORY + clusterId ;
79+ try {
80+ XdsResourceManager .parseYaml (body , Cluster .newBuilder ());
81+ } catch (IOException e ) {
82+ return CompletableFuture .completedFuture (
83+ XdsResourceManager .errorResponse (HttpStatus .BAD_REQUEST ,
84+ "Invalid request body: " + e .getMessage ()));
85+ }
86+ final String createSummary = isNullOrEmpty (summary ) ? "Create cluster: " + clusterName : summary ;
87+ final String normalizedBody = XdsResourceManager .normalizeYamlKeys (body );
88+ final String bodyToStore = XdsResourceManager .injectYamlField (normalizedBody , "name" , clusterName );
89+ return xdsResourceManager .push (group , clusterName , CLUSTERS_DIRECTORY + clusterId + ".yaml" ,
90+ createSummary , currentAuthor (), true , bodyToStore );
9891 }
9992
100- @ Override
101- public void deleteCluster (DeleteClusterRequest request , StreamObserver <Empty > responseObserver ) {
102- final String clusterName = request .getName ();
103- final String group = checkClusterName (clusterName ).group (1 );
104- xdsResourceManager .checkWritePermission (group );
105- final String deleteSummary = isNullOrEmpty (request .getSummary ()) ?
106- "Delete cluster: " + clusterName : request .getSummary ();
107- xdsResourceManager .delete (responseObserver , group , clusterName , deleteSummary , currentAuthor ());
93+ /**
94+ * PUT /xds/groups/{group}/clusters/{cluster_id}
95+ *
96+ * <p>Updates an existing cluster.
97+ */
98+ @ Put ("/xds/groups/{group}/clusters/{*cluster_id}" )
99+ @ Consumes ("application/yaml" )
100+ @ RequiresXdsGroupRole (RepositoryRole .WRITE )
101+ public CompletableFuture <HttpResponse > updateCluster (
102+ @ Param ("group" ) String group ,
103+ @ Param ("cluster_id" ) String clusterId ,
104+ @ Param ("summary" ) @ Nullable String summary ,
105+ String body ) {
106+ final String clusterName = "groups/" + group + "/clusters/" + clusterId ;
107+ if (!CLUSTER_NAME_PATTERN .matcher (clusterName ).matches ()) {
108+ return CompletableFuture .completedFuture (
109+ XdsResourceManager .errorResponse (HttpStatus .BAD_REQUEST ,
110+ "Invalid cluster name: " + clusterName ));
111+ }
112+ try {
113+ XdsResourceManager .parseYaml (body , Cluster .newBuilder ());
114+ } catch (IOException e ) {
115+ return CompletableFuture .completedFuture (
116+ XdsResourceManager .errorResponse (HttpStatus .BAD_REQUEST ,
117+ "Invalid request body: " + e .getMessage ()));
118+ }
119+ final String updateSummary = isNullOrEmpty (summary ) ? "Update cluster: " + clusterName : summary ;
120+ final String normalizedBody = XdsResourceManager .normalizeYamlKeys (body );
121+ final String bodyToStore = XdsResourceManager .injectYamlField (normalizedBody , "name" , clusterName );
122+ return xdsResourceManager .update (group , clusterName , updateSummary , currentAuthor (), bodyToStore );
108123 }
109124
110- private static Matcher checkClusterName (String clusterName ) {
111- final Matcher matcher = CLUSTER_NAME_PATTERN .matcher (clusterName );
112- if (!matcher .matches ()) {
113- throw Status .INVALID_ARGUMENT .withDescription ("Invalid cluster name: " + clusterName +
114- " (expected: " + CLUSTER_NAME_PATTERN + ')' )
115- .asRuntimeException ();
125+ /**
126+ * DELETE /xds/groups/{group}/clusters/{cluster_id}
127+ *
128+ * <p>Removes a cluster.
129+ */
130+ @ Delete ("/xds/groups/{group}/clusters/{*cluster_id}" )
131+ @ RequiresXdsGroupRole (RepositoryRole .WRITE )
132+ public CompletableFuture <HttpResponse > deleteCluster (
133+ @ Param ("group" ) String group ,
134+ @ Param ("cluster_id" ) String clusterId ,
135+ @ Param ("summary" ) @ Nullable String summary ) {
136+ final String clusterName = "groups/" + group + "/clusters/" + clusterId ;
137+ if (!CLUSTER_NAME_PATTERN .matcher (clusterName ).matches ()) {
138+ return CompletableFuture .completedFuture (
139+ XdsResourceManager .errorResponse (HttpStatus .BAD_REQUEST ,
140+ "Invalid cluster name: " + clusterName ));
116141 }
117- return matcher ;
142+ final String deleteSummary = isNullOrEmpty (summary ) ? "Delete cluster: " + clusterName : summary ;
143+ return xdsResourceManager .delete (group , clusterName , deleteSummary , currentAuthor ());
118144 }
119145}
0 commit comments