KNOX-1105 - Provide indication that topologies were generated from simple descriptors...
[knox.git] / gateway-service-admin / src / main / java / org / apache / hadoop / gateway / service / admin / TopologiesResource.java
1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package org.apache.hadoop.gateway.service.admin;
19
20 import com.fasterxml.jackson.annotation.JsonProperty;
21 import org.apache.commons.io.FileUtils;
22 import org.apache.commons.io.FilenameUtils;
23 import org.apache.hadoop.gateway.i18n.GatewaySpiMessages;
24 import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
25 import org.apache.hadoop.gateway.service.admin.beans.BeanConverter;
26 import org.apache.hadoop.gateway.service.admin.beans.Topology;
27 import org.apache.hadoop.gateway.services.GatewayServices;
28 import org.apache.hadoop.gateway.config.GatewayConfig;
29 import org.apache.hadoop.gateway.services.topology.TopologyService;
30
31 import javax.servlet.http.HttpServletRequest;
32 import javax.ws.rs.Consumes;
33 import javax.ws.rs.DELETE;
34 import javax.ws.rs.GET;
35 import javax.ws.rs.PUT;
36 import javax.ws.rs.Path;
37 import javax.ws.rs.PathParam;
38 import javax.ws.rs.Produces;
39 import javax.ws.rs.core.Context;
40 import javax.ws.rs.core.Response;
41 import javax.xml.bind.annotation.XmlAccessType;
42 import javax.xml.bind.annotation.XmlAccessorType;
43 import javax.xml.bind.annotation.XmlElement;
44 import javax.xml.bind.annotation.XmlElementWrapper;
45 import java.io.File;
46 import java.io.IOException;
47 import java.net.URI;
48 import java.net.URISyntaxException;
49 import java.util.ArrayList;
50 import java.util.Collection;
51 import java.util.Collections;
52 import java.util.Comparator;
53 import java.util.List;
54
55 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
56 import static javax.ws.rs.core.MediaType.APPLICATION_XML;
57 import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
58
59 import static javax.ws.rs.core.Response.ok;
60 import static javax.ws.rs.core.Response.created;
61 import static javax.ws.rs.core.Response.notModified;
62 import static javax.ws.rs.core.Response.status;
63
64
65 @Path("/api/v1")
66 public class TopologiesResource {
67
68 private static final String XML_EXT = ".xml";
69 private static final String JSON_EXT = ".json";
70
71 private static final String TOPOLOGIES_API_PATH = "topologies";
72 private static final String SINGLE_TOPOLOGY_API_PATH = TOPOLOGIES_API_PATH + "/{id}";
73 private static final String PROVIDERCONFIG_API_PATH = "providerconfig";
74 private static final String SINGLE_PROVIDERCONFIG_API_PATH = PROVIDERCONFIG_API_PATH + "/{name}";
75 private static final String DESCRIPTORS_API_PATH = "descriptors";
76 private static final String SINGLE_DESCRIPTOR_API_PATH = DESCRIPTORS_API_PATH + "/{name}";
77
78 private static GatewaySpiMessages log = MessagesFactory.get(GatewaySpiMessages.class);
79
80 @Context
81 private HttpServletRequest request;
82
83 @GET
84 @Produces({APPLICATION_JSON, APPLICATION_XML})
85 @Path(SINGLE_TOPOLOGY_API_PATH)
86 public Topology getTopology(@PathParam("id") String id) {
87 GatewayServices services = (GatewayServices) request.getServletContext()
88 .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
89 GatewayConfig config = (GatewayConfig) request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
90
91 TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
92
93 for (org.apache.hadoop.gateway.topology.Topology t : ts.getTopologies()) {
94 if(t.getName().equals(id)) {
95 try {
96 t.setUri(new URI( buildURI(t, config, request) ));
97 } catch (URISyntaxException se) {
98 t.setUri(null);
99 }
100 return BeanConverter.getTopology(t);
101 }
102 }
103 return null;
104 }
105
106 @GET
107 @Produces({APPLICATION_JSON, APPLICATION_XML})
108 @Path(TOPOLOGIES_API_PATH)
109 public SimpleTopologyWrapper getTopologies() {
110 GatewayServices services = (GatewayServices) request.getServletContext()
111 .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
112
113
114 TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
115
116 ArrayList<SimpleTopology> st = new ArrayList<SimpleTopology>();
117 GatewayConfig conf = (GatewayConfig) request.getServletContext().getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
118
119 for (org.apache.hadoop.gateway.topology.Topology t : ts.getTopologies()) {
120 st.add(getSimpleTopology(t, conf));
121 }
122
123 Collections.sort(st, new TopologyComparator());
124 SimpleTopologyWrapper stw = new SimpleTopologyWrapper();
125
126 for(SimpleTopology t : st){
127 stw.topologies.add(t);
128 }
129
130 return stw;
131
132 }
133
134 @PUT
135 @Consumes({APPLICATION_JSON, APPLICATION_XML})
136 @Path(SINGLE_TOPOLOGY_API_PATH)
137 public Topology uploadTopology(@PathParam("id") String id, Topology t) {
138 Topology result = null;
139
140 GatewayServices gs =
141 (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
142
143 t.setName(id);
144 TopologyService ts = gs.getService(GatewayServices.TOPOLOGY_SERVICE);
145
146 // Check for existing topology with the same name, to see if it had been generated
147 boolean existingGenerated = false;
148 for (org.apache.hadoop.gateway.topology.Topology existingTopology : ts.getTopologies()) {
149 if(existingTopology.getName().equals(id)) {
150 existingGenerated = existingTopology.isGenerated();
151 break;
152 }
153 }
154
155 // If a topology with the same ID exists, which had been generated, then DO NOT overwrite it because it will be
156 // out of sync with the source descriptor. Otherwise, deploy the updated version.
157 if (!existingGenerated) {
158 ts.deployTopology(BeanConverter.getTopology(t));
159 result = getTopology(id);
160 } else {
161 log.disallowedOverwritingGeneratedTopology(id);
162 }
163
164 return result;
165 }
166
167 @DELETE
168 @Produces(APPLICATION_JSON)
169 @Path(SINGLE_TOPOLOGY_API_PATH)
170 public Response deleteTopology(@PathParam("id") String id) {
171 boolean deleted = false;
172 if(!"admin".equals(id)) {
173 GatewayServices services = (GatewayServices) request.getServletContext()
174 .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
175
176 TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
177
178 for (org.apache.hadoop.gateway.topology.Topology t : ts.getTopologies()) {
179 if(t.getName().equals(id)) {
180 ts.deleteTopology(t);
181 deleted = true;
182 }
183 }
184 }else{
185 deleted = false;
186 }
187 return ok().entity("{ \"deleted\" : " + deleted + " }").build();
188 }
189
190 @GET
191 @Produces({APPLICATION_JSON})
192 @Path(PROVIDERCONFIG_API_PATH)
193 public HrefListing getProviderConfigurations() {
194 HrefListing listing = new HrefListing();
195 listing.setHref(buildHref(request));
196
197 GatewayServices services =
198 (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
199
200 List<HrefListItem> configs = new ArrayList<>();
201 TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
202 // Get all the simple descriptor file names
203 for (File providerConfig : ts.getProviderConfigurations()){
204 String id = FilenameUtils.getBaseName(providerConfig.getName());
205 configs.add(new HrefListItem(buildHref(id, request), providerConfig.getName()));
206 }
207
208 listing.setItems(configs);
209 return listing;
210 }
211
212 @GET
213 @Produces({APPLICATION_XML})
214 @Path(SINGLE_PROVIDERCONFIG_API_PATH)
215 public Response getProviderConfiguration(@PathParam("name") String name) {
216 Response response;
217
218 GatewayServices services =
219 (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
220
221 TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
222
223 File providerConfigFile = null;
224
225 for (File pc : ts.getProviderConfigurations()){
226 // If the file name matches the specified id
227 if (FilenameUtils.getBaseName(pc.getName()).equals(name)) {
228 providerConfigFile = pc;
229 break;
230 }
231 }
232
233 if (providerConfigFile != null) {
234 byte[] content = null;
235 try {
236 content = FileUtils.readFileToByteArray(providerConfigFile);
237 response = ok().entity(content).build();
238 } catch (IOException e) {
239 log.failedToReadConfigurationFile(providerConfigFile.getAbsolutePath(), e);
240 response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
241 }
242
243 } else {
244 response = Response.status(Response.Status.NOT_FOUND).build();
245 }
246 return response;
247 }
248
249 @DELETE
250 @Produces(APPLICATION_JSON)
251 @Path(SINGLE_PROVIDERCONFIG_API_PATH)
252 public Response deleteProviderConfiguration(@PathParam("name") String name) {
253 Response response;
254 GatewayServices services =
255 (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
256
257 TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
258 if (ts.deleteProviderConfiguration(name)) {
259 response = ok().entity("{ \"deleted\" : \"provider config " + name + "\" }").build();
260 } else {
261 response = notModified().build();
262 }
263 return response;
264 }
265
266
267 @DELETE
268 @Produces(APPLICATION_JSON)
269 @Path(SINGLE_DESCRIPTOR_API_PATH)
270 public Response deleteSimpleDescriptor(@PathParam("name") String name) {
271 Response response = null;
272 if(!"admin".equals(name)) {
273 GatewayServices services =
274 (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
275
276 TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
277 if (ts.deleteDescriptor(name)) {
278 response = ok().entity("{ \"deleted\" : \"descriptor " + name + "\" }").build();
279 }
280 }
281
282 if (response == null) {
283 response = notModified().build();
284 }
285
286 return response;
287 }
288
289
290 @PUT
291 @Consumes({APPLICATION_XML})
292 @Path(SINGLE_PROVIDERCONFIG_API_PATH)
293 public Response uploadProviderConfiguration(@PathParam("name") String name, String content) {
294 Response response = null;
295
296 GatewayServices gs =
297 (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
298
299 TopologyService ts = gs.getService(GatewayServices.TOPOLOGY_SERVICE);
300
301 boolean isUpdate = configFileExists(ts.getProviderConfigurations(), name);
302
303 String filename = name.endsWith(XML_EXT) ? name : name + XML_EXT;
304 if (ts.deployProviderConfiguration(filename, content)) {
305 try {
306 if (isUpdate) {
307 response = Response.noContent().build();
308 } else{
309 response = created(new URI(buildHref(request))).build();
310 }
311 } catch (URISyntaxException e) {
312 log.invalidResourceURI(e.getInput(), e.getReason(), e);
313 response = status(Response.Status.BAD_REQUEST).entity("{ \"error\" : \"Failed to deploy provider configuration " + name + "\" }").build();
314 }
315 }
316
317 return response;
318 }
319
320
321 private boolean configFileExists(Collection<File> existing, String candidateName) {
322 boolean result = false;
323 for (File exists : existing) {
324 if (FilenameUtils.getBaseName(exists.getName()).equals(candidateName)) {
325 result = true;
326 break;
327 }
328 }
329 return result;
330 }
331
332
333 @PUT
334 @Consumes({APPLICATION_JSON})
335 @Path(SINGLE_DESCRIPTOR_API_PATH)
336 public Response uploadSimpleDescriptor(@PathParam("name") String name, String content) {
337 Response response = null;
338
339 GatewayServices gs =
340 (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
341
342 TopologyService ts = gs.getService(GatewayServices.TOPOLOGY_SERVICE);
343
344 boolean isUpdate = configFileExists(ts.getDescriptors(), name);
345
346 String filename = name.endsWith(JSON_EXT) ? name : name + JSON_EXT;
347 if (ts.deployDescriptor(filename, content)) {
348 try {
349 if (isUpdate) {
350 response = Response.noContent().build();
351 } else {
352 response = created(new URI(buildHref(request))).build();
353 }
354 } catch (URISyntaxException e) {
355 log.invalidResourceURI(e.getInput(), e.getReason(), e);
356 response = status(Response.Status.BAD_REQUEST).entity("{ \"error\" : \"Failed to deploy descriptor " + name + "\" }").build();
357 }
358 }
359
360 return response;
361 }
362
363
364 @GET
365 @Produces({APPLICATION_JSON})
366 @Path(DESCRIPTORS_API_PATH)
367 public HrefListing getSimpleDescriptors() {
368 HrefListing listing = new HrefListing();
369 listing.setHref(buildHref(request));
370
371 GatewayServices services =
372 (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
373
374 List<HrefListItem> descriptors = new ArrayList<>();
375 TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
376 for (File descriptor : ts.getDescriptors()){
377 String id = FilenameUtils.getBaseName(descriptor.getName());
378 descriptors.add(new HrefListItem(buildHref(id, request), descriptor.getName()));
379 }
380
381 listing.setItems(descriptors);
382 return listing;
383 }
384
385
386 @GET
387 @Produces({APPLICATION_JSON, TEXT_PLAIN})
388 @Path(SINGLE_DESCRIPTOR_API_PATH)
389 public Response getSimpleDescriptor(@PathParam("name") String name) {
390 Response response;
391
392 GatewayServices services =
393 (GatewayServices) request.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
394
395 TopologyService ts = services.getService(GatewayServices.TOPOLOGY_SERVICE);
396
397 File descriptorFile = null;
398
399 for (File sd : ts.getDescriptors()){
400 // If the file name matches the specified id
401 if (FilenameUtils.getBaseName(sd.getName()).equals(name)) {
402 descriptorFile = sd;
403 break;
404 }
405 }
406
407 if (descriptorFile != null) {
408 String mediaType = APPLICATION_JSON;
409
410 byte[] content = null;
411 try {
412 if ("yml".equals(FilenameUtils.getExtension(descriptorFile.getName()))) {
413 mediaType = TEXT_PLAIN;
414 }
415 content = FileUtils.readFileToByteArray(descriptorFile);
416 response = ok().type(mediaType).entity(content).build();
417 } catch (IOException e) {
418 log.failedToReadConfigurationFile(descriptorFile.getAbsolutePath(), e);
419 response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
420 }
421 } else {
422 response = Response.status(Response.Status.NOT_FOUND).build();
423 }
424
425 return response;
426 }
427
428
429 private static class TopologyComparator implements Comparator<SimpleTopology> {
430 @Override
431 public int compare(SimpleTopology t1, SimpleTopology t2) {
432 return t1.getName().compareTo(t2.getName());
433 }
434 }
435
436
437 String buildURI(org.apache.hadoop.gateway.topology.Topology topology, GatewayConfig config, HttpServletRequest req){
438 String uri = buildXForwardBaseURL(req);
439
440 // Strip extra context
441 uri = uri.replace(req.getContextPath(), "");
442
443 // Add the gateway path
444 String gatewayPath;
445 if(config.getGatewayPath() != null){
446 gatewayPath = config.getGatewayPath();
447 }else{
448 gatewayPath = "gateway";
449 }
450 uri += "/" + gatewayPath;
451
452 uri += "/" + topology.getName();
453 return uri;
454 }
455
456 String buildHref(HttpServletRequest req) {
457 return buildHref((String)null, req);
458 }
459
460 String buildHref(String id, HttpServletRequest req) {
461 String href = buildXForwardBaseURL(req);
462 // Make sure that the pathInfo doesn't have any '/' chars at the end.
463 String pathInfo = req.getPathInfo();
464 while(pathInfo.endsWith("/")) {
465 pathInfo = pathInfo.substring(0, pathInfo.length() - 1);
466 }
467
468 href += pathInfo;
469
470 if (id != null) {
471 href += "/" + id;
472 }
473
474 return href;
475 }
476
477 String buildHref(org.apache.hadoop.gateway.topology.Topology t, HttpServletRequest req) {
478 return buildHref(t.getName(), req);
479 }
480
481 private SimpleTopology getSimpleTopology(org.apache.hadoop.gateway.topology.Topology t, GatewayConfig config) {
482 String uri = buildURI(t, config, request);
483 String href = buildHref(t, request);
484 return new SimpleTopology(t, uri, href);
485 }
486
487 private String buildXForwardBaseURL(HttpServletRequest req){
488 final String X_Forwarded = "X-Forwarded-";
489 final String X_Forwarded_Context = X_Forwarded + "Context";
490 final String X_Forwarded_Proto = X_Forwarded + "Proto";
491 final String X_Forwarded_Host = X_Forwarded + "Host";
492 final String X_Forwarded_Port = X_Forwarded + "Port";
493 final String X_Forwarded_Server = X_Forwarded + "Server";
494
495 String baseURL = "";
496
497 // Get Protocol
498 if(req.getHeader(X_Forwarded_Proto) != null){
499 baseURL += req.getHeader(X_Forwarded_Proto) + "://";
500 } else {
501 baseURL += req.getProtocol() + "://";
502 }
503
504 // Handle Server/Host and Port Here
505 if (req.getHeader(X_Forwarded_Host) != null && req.getHeader(X_Forwarded_Port) != null){
506 // Double check to see if host has port
507 if(req.getHeader(X_Forwarded_Host).contains(req.getHeader(X_Forwarded_Port))){
508 baseURL += req.getHeader(X_Forwarded_Host);
509 } else {
510 // If there's no port, add the host and port together;
511 baseURL += req.getHeader(X_Forwarded_Host) + ":" + req.getHeader(X_Forwarded_Port);
512 }
513 } else if(req.getHeader(X_Forwarded_Server) != null && req.getHeader(X_Forwarded_Port) != null){
514 // Tack on the server and port if they're available. Try host if server not available
515 baseURL += req.getHeader(X_Forwarded_Server) + ":" + req.getHeader(X_Forwarded_Port);
516 } else if(req.getHeader(X_Forwarded_Port) != null) {
517 // if we at least have a port, we can use it.
518 baseURL += req.getServerName() + ":" + req.getHeader(X_Forwarded_Port);
519 } else {
520 // Resort to request members
521 baseURL += req.getServerName() + ":" + req.getLocalPort();
522 }
523
524 // Handle Server context
525 if( req.getHeader(X_Forwarded_Context) != null ) {
526 baseURL += req.getHeader( X_Forwarded_Context );
527 } else {
528 baseURL += req.getContextPath();
529 }
530
531 return baseURL;
532 }
533
534
535 static class HrefListing {
536 @JsonProperty
537 String href;
538
539 @JsonProperty
540 List<HrefListItem> items;
541
542 HrefListing() {}
543
544 public void setHref(String href) {
545 this.href = href;
546 }
547
548 public String getHref() {
549 return href;
550 }
551
552 public void setItems(List<HrefListItem> items) {
553 this.items = items;
554 }
555
556 public List<HrefListItem> getItems() {
557 return items;
558 }
559 }
560
561 static class HrefListItem {
562 @JsonProperty
563 String href;
564
565 @JsonProperty
566 String name;
567
568 HrefListItem() {}
569
570 HrefListItem(String href, String name) {
571 this.href = href;
572 this.name = name;
573 }
574
575 public void setHref(String href) {
576 this.href = href;
577 }
578
579 public String getHref() {
580 return href;
581 }
582
583 public void setName(String name) {
584 this.name = name;
585 }
586 public String getName() {
587 return name;
588 }
589 }
590
591
592 @XmlAccessorType(XmlAccessType.NONE)
593 public static class SimpleTopology {
594
595 @XmlElement
596 private String name;
597 @XmlElement
598 private String timestamp;
599 @XmlElement
600 private String defaultServicePath;
601 @XmlElement
602 private String uri;
603 @XmlElement
604 private String href;
605
606 public SimpleTopology() {}
607
608 public SimpleTopology(org.apache.hadoop.gateway.topology.Topology t, String uri, String href) {
609 this.name = t.getName();
610 this.timestamp = Long.toString(t.getTimestamp());
611 this.defaultServicePath = t.getDefaultServicePath();
612 this.uri = uri;
613 this.href = href;
614 }
615
616 public String getName() {
617 return name;
618 }
619
620 public void setName(String n) {
621 name = n;
622 }
623
624 public String getTimestamp() {
625 return timestamp;
626 }
627
628 public void setDefaultService(String defaultServicePath) {
629 this.defaultServicePath = defaultServicePath;
630 }
631
632 public String getDefaultService() {
633 return defaultServicePath;
634 }
635
636 public void setTimestamp(String timestamp) {
637 this.timestamp = timestamp;
638 }
639
640 public String getUri() {
641 return uri;
642 }
643
644 public void setUri(String uri) {
645 this.uri = uri;
646 }
647
648 public String getHref() {
649 return href;
650 }
651
652 public void setHref(String href) {
653 this.href = href;
654 }
655 }
656
657 @XmlAccessorType(XmlAccessType.FIELD)
658 public static class SimpleTopologyWrapper{
659
660 @XmlElement(name="topology")
661 @XmlElementWrapper(name="topologies")
662 private List<SimpleTopology> topologies = new ArrayList<SimpleTopology>();
663
664 public List<SimpleTopology> getTopologies(){
665 return topologies;
666 }
667
668 public void setTopologies(List<SimpleTopology> ts){
669 this.topologies = ts;
670 }
671
672 }
673 }
674