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,91 @@ 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 );
78+ final String clusterName = clusterName (group , 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 bodyToStore = XdsResourceManager .injectYamlField (body , "name" , clusterName );
88+ return xdsResourceManager .push (group , clusterName , CLUSTERS_DIRECTORY + clusterId + ".yaml" ,
89+ createSummary , currentAuthor (), true , bodyToStore );
8690 }
8791
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 ());
92+ /**
93+ * PUT /xds/groups/{group}/clusters/{cluster_id}
94+ *
95+ * <p>Updates an existing cluster.
96+ */
97+ @ Put ("/xds/groups/{group}/clusters/{*cluster_id}" )
98+ @ Consumes ("application/yaml" )
99+ @ RequiresXdsGroupRole (RepositoryRole .WRITE )
100+ public CompletableFuture <HttpResponse > updateCluster (
101+ @ Param ("group" ) String group ,
102+ @ Param ("cluster_id" ) String clusterId ,
103+ @ Param ("summary" ) @ Nullable String summary ,
104+ String body ) {
105+ final String clusterName = clusterName (group , clusterId );
106+ if (!CLUSTER_NAME_PATTERN .matcher (clusterName ).matches ()) {
107+ return CompletableFuture .completedFuture (
108+ XdsResourceManager .errorResponse (HttpStatus .BAD_REQUEST ,
109+ "Invalid cluster name: " + clusterName ));
110+ }
111+ try {
112+ XdsResourceManager .parseYaml (body , Cluster .newBuilder ());
113+ } catch (IOException e ) {
114+ return CompletableFuture .completedFuture (
115+ XdsResourceManager .errorResponse (HttpStatus .BAD_REQUEST ,
116+ "Invalid request body: " + e .getMessage ()));
117+ }
118+ final String updateSummary = isNullOrEmpty (summary ) ? "Update cluster: " + clusterName : summary ;
119+ final String bodyToStore = XdsResourceManager .injectYamlField (body , "name" , clusterName );
120+ return xdsResourceManager .update (group , clusterName , updateSummary , currentAuthor (), bodyToStore );
98121 }
99122
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 ());
123+ /**
124+ * DELETE /xds/groups/{group}/clusters/{cluster_id}
125+ *
126+ * <p>Removes a cluster.
127+ */
128+ @ Delete ("/xds/groups/{group}/clusters/{*cluster_id}" )
129+ @ RequiresXdsGroupRole (RepositoryRole .WRITE )
130+ public CompletableFuture <HttpResponse > deleteCluster (
131+ @ Param ("group" ) String group ,
132+ @ Param ("cluster_id" ) String clusterId ,
133+ @ Param ("summary" ) @ Nullable String summary ) {
134+ final String clusterName = clusterName (group , clusterId );
135+ if (!CLUSTER_NAME_PATTERN .matcher (clusterName ).matches ()) {
136+ return CompletableFuture .completedFuture (
137+ XdsResourceManager .errorResponse (HttpStatus .BAD_REQUEST ,
138+ "Invalid cluster name: " + clusterName ));
139+ }
140+ final String deleteSummary = isNullOrEmpty (summary ) ? "Delete cluster: " + clusterName : summary ;
141+ return xdsResourceManager .delete (group , clusterName , deleteSummary , currentAuthor ());
108142 }
109143
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 ();
116- }
117- return matcher ;
144+ private static String clusterName (String group , String clusterId ) {
145+ return "groups/" + group + CLUSTERS_DIRECTORY + clusterId ;
118146 }
119147}
0 commit comments