1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.serviceregistry.impl.endpoint;
23
24 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
25 import static javax.servlet.http.HttpServletResponse.SC_CREATED;
26 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
27 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
28 import static javax.servlet.http.HttpServletResponse.SC_OK;
29 import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
30 import static org.apache.commons.lang3.StringUtils.isBlank;
31 import static org.apache.commons.lang3.StringUtils.isNotBlank;
32
33 import org.opencastproject.job.api.JaxbJob;
34 import org.opencastproject.job.api.JaxbJobList;
35 import org.opencastproject.job.api.Job;
36 import org.opencastproject.job.api.JobParser;
37 import org.opencastproject.rest.RestConstants;
38 import org.opencastproject.security.api.SecurityService;
39 import org.opencastproject.serviceregistry.api.HostRegistration;
40 import org.opencastproject.serviceregistry.api.JaxbHostRegistration;
41 import org.opencastproject.serviceregistry.api.JaxbHostRegistrationList;
42 import org.opencastproject.serviceregistry.api.JaxbServiceHealth;
43 import org.opencastproject.serviceregistry.api.JaxbServiceRegistration;
44 import org.opencastproject.serviceregistry.api.JaxbServiceRegistrationList;
45 import org.opencastproject.serviceregistry.api.JaxbServiceStatisticsList;
46 import org.opencastproject.serviceregistry.api.ServiceRegistration;
47 import org.opencastproject.serviceregistry.api.ServiceRegistry;
48 import org.opencastproject.serviceregistry.api.ServiceRegistryException;
49 import org.opencastproject.serviceregistry.api.ServiceState;
50 import org.opencastproject.serviceregistry.api.SystemLoad;
51 import org.opencastproject.serviceregistry.impl.ServiceRegistryJpaImpl;
52 import org.opencastproject.systems.OpencastConstants;
53 import org.opencastproject.util.NotFoundException;
54 import org.opencastproject.util.UrlSupport;
55 import org.opencastproject.util.doc.rest.RestParameter;
56 import org.opencastproject.util.doc.rest.RestParameter.Type;
57 import org.opencastproject.util.doc.rest.RestQuery;
58 import org.opencastproject.util.doc.rest.RestResponse;
59 import org.opencastproject.util.doc.rest.RestService;
60
61 import org.apache.commons.lang3.StringUtils;
62 import org.json.simple.JSONArray;
63 import org.json.simple.JSONValue;
64 import org.osgi.service.component.ComponentContext;
65 import org.osgi.service.component.annotations.Activate;
66 import org.osgi.service.component.annotations.Component;
67 import org.osgi.service.component.annotations.Reference;
68 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
69
70 import java.net.MalformedURLException;
71 import java.net.URL;
72 import java.util.Arrays;
73 import java.util.Collections;
74 import java.util.LinkedList;
75 import java.util.List;
76 import java.util.Map;
77
78 import javax.servlet.http.HttpServletRequest;
79 import javax.servlet.http.HttpServletResponse;
80 import javax.ws.rs.DELETE;
81 import javax.ws.rs.FormParam;
82 import javax.ws.rs.GET;
83 import javax.ws.rs.POST;
84 import javax.ws.rs.PUT;
85 import javax.ws.rs.Path;
86 import javax.ws.rs.PathParam;
87 import javax.ws.rs.Produces;
88 import javax.ws.rs.QueryParam;
89 import javax.ws.rs.WebApplicationException;
90 import javax.ws.rs.core.Context;
91 import javax.ws.rs.core.MediaType;
92 import javax.ws.rs.core.Response;
93 import javax.ws.rs.core.Response.Status;
94
95
96
97
98 @Path("/services")
99 @RestService(
100 name = "serviceregistry",
101 title = "Service Registry",
102 notes = { "All paths above are relative to the REST endpoint base" },
103 abstractText = "Provides registration and management functions for servers and services in this Opencast instance "
104 + "or cluster.")
105 @Component(
106 property = {
107 "service.description=Service Registry REST Endpoint",
108 "opencast.service.type=org.opencastproject.serviceregistry",
109 "opencast.service.path=/services"
110 },
111 immediate = true,
112 service = { ServiceRegistryEndpoint.class }
113 )
114 @JaxrsResource
115 public class ServiceRegistryEndpoint {
116
117
118 protected ServiceRegistry serviceRegistry = null;
119
120 private SecurityService securityService = null;
121
122
123 protected String serverUrl = UrlSupport.DEFAULT_BASE_URL;
124
125
126 protected String servicePath = "/";
127
128
129 @Reference
130 public void setServiceRegistry(ServiceRegistry serviceRegistry) {
131 this.serviceRegistry = serviceRegistry;
132 }
133
134 @Reference
135 public void setSecurityService(SecurityService securityService) {
136 this.securityService = securityService;
137 }
138
139
140
141
142
143
144
145 @Activate
146 public void activate(ComponentContext cc) {
147 serverUrl = cc.getBundleContext().getProperty(OpencastConstants.SERVER_URL_PROPERTY);
148 servicePath = (String) cc.getProperties().get(RestConstants.SERVICE_PATH_PROPERTY);
149 }
150
151 @GET
152 @Path("statistics.json")
153 @Produces(MediaType.APPLICATION_JSON)
154 @RestQuery(
155 name = "statisticsasjson",
156 description = "List the service registrations in the cluster, along with some simple statistics",
157 returnDescription = "The service statistics.",
158 responses = {
159 @RestResponse(responseCode = SC_OK, description = "A JSON representation of the service statistics")
160 })
161 public Response getStatisticsAsJson() {
162 try {
163 return Response.ok(new JaxbServiceStatisticsList(serviceRegistry.getServiceStatistics())).build();
164 } catch (ServiceRegistryException e) {
165 throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR);
166 }
167 }
168
169 @GET
170 @Path("statistics.xml")
171 @Produces(MediaType.TEXT_XML)
172 @RestQuery(
173 name = "statisticsasxml",
174 description = "List the service registrations in the cluster, along with some simple statistics",
175 returnDescription = "The service statistics.",
176 responses = {
177 @RestResponse(responseCode = SC_OK, description = "An XML representation of the service statistics")
178 })
179 public Response getStatisticsAsXml() throws ServiceRegistryException {
180 return getStatisticsAsJson();
181 }
182
183 @POST
184 @Path("sanitize")
185 @RestQuery(
186 name = "sanitize",
187 description = "Sets the given service to NORMAL state",
188 returnDescription = "No content",
189 restParameters = {
190 @RestParameter(name = "serviceType", isRequired = true, description = "The service type identifier",
191 type = Type.STRING, defaultValue = ""),
192 @RestParameter(name = "host", isRequired = true, description = "The host providing the service, "
193 + "including the http(s) protocol", type = Type.STRING, defaultValue = "")
194 },
195 responses = {
196 @RestResponse(responseCode = SC_NO_CONTENT, description = "The service was successfully sanitized"),
197 @RestResponse(responseCode = SC_NOT_FOUND,
198 description = "No service of that type on that host is registered.")
199 })
200 public Response sanitize(@FormParam("serviceType") String serviceType, @FormParam("host") String host)
201 throws NotFoundException {
202 serviceRegistry.sanitize(serviceType, host);
203 return Response.status(Status.NO_CONTENT).build();
204 }
205
206 @POST
207 @Path("register")
208 @Produces(MediaType.TEXT_XML)
209 @RestQuery(
210 name = "register",
211 description = "Add a new service registration to the cluster.",
212 returnDescription = "The service registration.",
213 restParameters = {
214 @RestParameter(name = "serviceType", isRequired = true, description = "The service type identifier",
215 type = Type.STRING, defaultValue = ""),
216 @RestParameter(name = "host", isRequired = true, description = "The host providing the service, "
217 + "including the http(s) protocol", type = Type.STRING, defaultValue = ""),
218 @RestParameter(name = "path", isRequired = true, description = "The service path on the host",
219 type = Type.STRING, defaultValue = ""),
220 @RestParameter(name = "jobProducer", isRequired = true, description = "Whether this service is a producer of "
221 + "long running jobs requiring dispatch", type = Type.STRING, defaultValue = "false")
222 },
223 responses = {
224 @RestResponse(responseCode = SC_OK, description = "An XML representation of the new service registration")
225 })
226 public JaxbServiceRegistration register(@FormParam("serviceType") String serviceType, @FormParam("host") String host,
227 @FormParam("path") String path, @FormParam("jobProducer") boolean jobProducer) {
228 try {
229 return new JaxbServiceRegistration(serviceRegistry.registerService(serviceType, host, path, jobProducer));
230 } catch (ServiceRegistryException e) {
231 throw new WebApplicationException(e);
232 }
233 }
234
235 @POST
236 @Path("unregister")
237 @RestQuery(
238 name = "unregister",
239 description = "Removes a service registration.",
240 returnDescription = "No content",
241 restParameters = {
242 @RestParameter(name = "serviceType", isRequired = true, description = "The service type identifier",
243 type = Type.STRING),
244 @RestParameter(name = "host", isRequired = true, description = "The host providing the service, "
245 + "including the http(s) protocol", type = Type.STRING)
246 },
247 responses = {
248 @RestResponse(responseCode = SC_NO_CONTENT, description = "The service was unregistered successfully")
249 })
250 public Response unregister(@FormParam("serviceType") String serviceType, @FormParam("host") String host) {
251 try {
252 serviceRegistry.unRegisterService(serviceType, host);
253 return Response.status(Status.NO_CONTENT).build();
254 } catch (ServiceRegistryException e) {
255 throw new WebApplicationException(e);
256 }
257 }
258
259 @POST
260 @Path("enablehost")
261 @RestQuery(
262 name = "enablehost",
263 description = "Enable a server from the cluster.",
264 returnDescription = "No content.",
265 restParameters = {
266 @RestParameter(name = "host", isRequired = true,
267 description = "The host name, including the http(s) protocol", type = Type.STRING)
268 },
269 responses = {
270 @RestResponse(responseCode = SC_NO_CONTENT, description = "The host was enabled successfully"),
271 @RestResponse(responseCode = SC_NOT_FOUND, description = "The host does not exist")
272 })
273 public Response enableHost(@FormParam("host") String host) throws NotFoundException {
274 try {
275 serviceRegistry.enableHost(host);
276 return Response.status(Status.NO_CONTENT).build();
277 } catch (ServiceRegistryException e) {
278 throw new WebApplicationException(e);
279 }
280 }
281
282 @POST
283 @Path("disablehost")
284 @RestQuery(
285 name = "disablehost",
286 description = "Disable a server from the cluster.",
287 returnDescription = "No content.",
288 restParameters = {
289 @RestParameter(name = "host", isRequired = true,
290 description = "The host name, including the http(s) protocol", type = Type.STRING)
291 },
292 responses = {
293 @RestResponse(responseCode = SC_NO_CONTENT, description = "The host was disabled successfully"),
294 @RestResponse(responseCode = SC_NOT_FOUND, description = "The host does not exist")
295 })
296 public Response disableHost(@FormParam("host") String host) throws NotFoundException {
297 try {
298 serviceRegistry.disableHost(host);
299 return Response.status(Status.NO_CONTENT).build();
300 } catch (ServiceRegistryException e) {
301 throw new WebApplicationException(e);
302 }
303 }
304
305 @POST
306 @Path("registerhost")
307 @RestQuery(
308 name = "registerhost",
309 description = "Add a new server to the cluster.",
310 returnDescription = "No content.",
311 restParameters = {
312 @RestParameter(name = "host", isRequired = true,
313 description = "The host name, including the http(s) protocol", type = Type.STRING),
314 @RestParameter(name = "address", isRequired = true, description = "The IP address", type = Type.STRING),
315 @RestParameter(name = "nodeName", isRequired = true,
316 description = "Descriptive node name", type = Type.STRING),
317 @RestParameter(name = "memory", isRequired = true, description = "The allocated memory", type = Type.STRING),
318 @RestParameter(name = "cores", isRequired = true, description = "The available cores", type = Type.STRING),
319 @RestParameter(name = "maxLoad", isRequired = true, description = "The maximum load this host support",
320 type = Type.STRING)
321 },
322 responses = {
323 @RestResponse(responseCode = SC_NO_CONTENT, description = "The host was registered successfully")
324 })
325 public void register(@FormParam("host") String host, @FormParam("address") String address,
326 @FormParam("nodeName") String nodeName, @FormParam("memory") long memory, @FormParam("cores") int cores,
327 @FormParam("maxLoad") float maxLoad) {
328 try {
329 serviceRegistry.registerHost(host, address, nodeName, memory, cores, maxLoad);
330 } catch (ServiceRegistryException e) {
331 throw new WebApplicationException(e);
332 }
333 }
334
335 @POST
336 @Path("unregisterhost")
337 @RestQuery(
338 name = "unregisterhost",
339 description = "Removes a server from the cluster.",
340 returnDescription = "No content.",
341 restParameters = {
342 @RestParameter(name = "host", isRequired = true, description = "The host name, including the http(s) protocol",
343 type = Type.STRING)
344 },
345 responses = {
346 @RestResponse(responseCode = SC_NO_CONTENT, description = "The host was removed successfully")
347 })
348 public Response unregister(@FormParam("host") String host) {
349 try {
350 serviceRegistry.unregisterHost(host);
351 return Response.status(Status.NO_CONTENT).build();
352 } catch (ServiceRegistryException e) {
353 if (e.getCause() instanceof IllegalArgumentException) {
354 return Response.status(Status.NOT_FOUND).entity(e.getMessage()).build();
355 }
356 throw new WebApplicationException(e);
357 }
358 }
359
360 @POST
361 @Path("maintenance")
362 @RestQuery(
363 name = "maintenance",
364 description = "Sets the maintenance status for a server in the cluster.",
365 returnDescription = "No content.",
366 restParameters = {
367 @RestParameter(name = "host", isRequired = true, type = Type.STRING,
368 description = "The host name, including the http(s) protocol"),
369 @RestParameter(name = "maintenance", isRequired = true, type = Type.BOOLEAN,
370 description = "Whether this host should be put into maintenance mode (true) or not")
371 }, responses = {
372 @RestResponse(responseCode = SC_NO_CONTENT, description = "The host was registered successfully"),
373 @RestResponse(responseCode = SC_NOT_FOUND, description = "Host not found")
374 })
375 public Response setMaintenanceMode(@FormParam("host") String host, @FormParam("maintenance") boolean maintenance)
376 throws NotFoundException {
377 try {
378 serviceRegistry.setMaintenanceStatus(host, maintenance);
379 return Response.status(Status.NO_CONTENT).build();
380 } catch (ServiceRegistryException e) {
381 throw new WebApplicationException(e);
382 }
383 }
384
385 @GET
386 @Path("available.xml")
387 @Produces(MediaType.TEXT_XML)
388 @RestQuery(
389 name = "availableasxml",
390 description = "Lists available services by service type identifier, ordered by load.",
391 returnDescription = "The services list as XML",
392 restParameters = {
393 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING,
394 description = "The service type identifier")
395 },
396 responses = {
397 @RestResponse(responseCode = SC_OK, description = "Returned the available services."),
398 @RestResponse(responseCode = SC_BAD_REQUEST, description = "No service type specified, bad request.")
399 })
400 public Response getAvailableServicesAsXml(@QueryParam("serviceType") String serviceType) {
401
402 if (isBlank(serviceType)) {
403 throw new WebApplicationException(
404 Response.status(Status.BAD_REQUEST).entity("Service type must be specified").build());
405 }
406
407 Map<String, String> properties = securityService.getOrganization().getProperties();
408
409 JaxbServiceRegistrationList registrations = new JaxbServiceRegistrationList();
410 try {
411 for (ServiceRegistration reg : serviceRegistry.getServiceRegistrationsByLoad(serviceType)) {
412 JaxbServiceRegistration jaxbReg = new JaxbServiceRegistration(reg);
413 URL internalHostUrl = new URL(jaxbReg.getHost());
414 String tenantSpecificHost = StringUtils.trimToNull(properties.get("org.opencastproject.host."
415 + internalHostUrl.getHost()));
416 if (StringUtils.isNotBlank(tenantSpecificHost)) {
417 jaxbReg.setHost(tenantSpecificHost);
418 }
419 registrations.add(jaxbReg);
420 }
421 return Response.ok(registrations).build();
422 } catch (ServiceRegistryException | MalformedURLException e) {
423 throw new WebApplicationException(e);
424 }
425 }
426
427 @GET
428 @Path("available.json")
429 @Produces(MediaType.APPLICATION_JSON)
430 @RestQuery(
431 name = "availableasjson",
432 description = "Lists available services by service type identifier, ordered by load.",
433 returnDescription = "The services list as JSON",
434 restParameters = {
435 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING,
436 description = "The service type identifier")
437 },
438 responses = {
439 @RestResponse(responseCode = SC_OK, description = "Returned the available services."),
440 @RestResponse(responseCode = SC_BAD_REQUEST, description = "No service type specified, bad request.")
441 })
442 public Response getAvailableServicesAsJson(@QueryParam("serviceType") String serviceType) {
443 return getAvailableServicesAsXml(serviceType);
444 }
445
446 @GET
447 @Path("health.json")
448 @Produces(MediaType.APPLICATION_JSON)
449 @RestQuery(
450 name = "health",
451 description = "Checks the status of the registered services",
452 returnDescription = "Returns NO_CONTENT if services are in a proper state",
453 restParameters = {
454 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING,
455 description = "The service type identifier"),
456 @RestParameter(name = "host", isRequired = false, type = Type.STRING,
457 description = "The host, including the http(s) protocol")
458 }, responses = {
459 @RestResponse(responseCode = SC_OK, description = "Service states returned"),
460 @RestResponse(responseCode = SC_NOT_FOUND,
461 description = "No service of that type on that host is registered."),
462 @RestResponse(responseCode = SC_SERVICE_UNAVAILABLE,
463 description = "An error has occurred during stats processing")
464 })
465 public Response getHealthStatusAsJson(@QueryParam("serviceType") String serviceType, @QueryParam("host") String host)
466 throws NotFoundException {
467 return getHealthStatus(serviceType, host);
468 }
469
470 @GET
471 @Path("health.xml")
472 @Produces(MediaType.APPLICATION_XML)
473 @RestQuery(
474 name = "health",
475 description = "Checks the status of the registered services",
476 returnDescription = "Returns NO_CONTENT if services are in a proper state",
477 restParameters = {
478 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING,
479 description = "The service type identifier"),
480 @RestParameter(name = "host", isRequired = false, type = Type.STRING,
481 description = "The host, including the http(s) protocol")
482 },
483 responses = {
484 @RestResponse(responseCode = SC_OK, description = "Service states returned"),
485 @RestResponse(responseCode = SC_NOT_FOUND,
486 description = "No service of that type on that host is registered."),
487 @RestResponse(responseCode = SC_SERVICE_UNAVAILABLE,
488 description = "An error has occurred during stats processing")
489 })
490 public Response getHealthStatus(@QueryParam("serviceType") String serviceType, @QueryParam("host") String host)
491 throws NotFoundException {
492 try {
493 List<ServiceRegistration> services = null;
494 if (isNotBlank(serviceType) && isNotBlank(host)) {
495
496 ServiceRegistration reg = serviceRegistry.getServiceRegistration(serviceType, host);
497 if (reg == null) {
498 throw new NotFoundException();
499 }
500
501 services = new LinkedList<ServiceRegistration>();
502 services.add(reg);
503 } else if (isBlank(serviceType) && isBlank(host)) {
504
505 services = serviceRegistry.getServiceRegistrations();
506 } else if (isNotBlank(serviceType)) {
507
508 services = serviceRegistry.getServiceRegistrationsByType(serviceType);
509 } else if (isNotBlank(host)) {
510
511 services = serviceRegistry.getServiceRegistrationsByHost(host);
512 }
513
514 int healthy = 0;
515 int warning = 0;
516 int error = 0;
517 for (ServiceRegistration reg : services) {
518 if (ServiceState.NORMAL == reg.getServiceState()) {
519 healthy++;
520 } else if (ServiceState.WARNING == reg.getServiceState()) {
521 warning++;
522 } else if (ServiceState.ERROR == reg.getServiceState()) {
523 error++;
524 } else {
525 error++;
526 }
527 }
528 JaxbServiceHealth stats = new JaxbServiceHealth(healthy, warning, error);
529 return Response.ok(stats).build();
530 } catch (ServiceRegistryException e) {
531 throw new WebApplicationException(e);
532 }
533 }
534
535 @GET
536 @Path("services.xml")
537 @Produces(MediaType.TEXT_XML)
538 @RestQuery(
539 name = "servicesasxml",
540 description = "Returns a service registraton or list of available service registrations as XML.",
541 returnDescription = "The services list as XML",
542 restParameters = {
543 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING,
544 description = "The service type identifier"),
545 @RestParameter(name = "host", isRequired = false, type = Type.STRING,
546 description = "The host, including the http(s) protocol")
547 },
548 responses = {
549 @RestResponse(responseCode = SC_OK, description = "Returned the available service."),
550 @RestResponse(responseCode = SC_NOT_FOUND,
551 description = "No service of that type on that host is registered.")
552 })
553 public JaxbServiceRegistrationList getRegistrationsAsXml(@QueryParam("serviceType") String serviceType,
554 @QueryParam("host") String host) throws NotFoundException {
555 JaxbServiceRegistrationList registrations = new JaxbServiceRegistrationList();
556 try {
557 if (isNotBlank(serviceType) && isNotBlank(host)) {
558
559 ServiceRegistration reg = serviceRegistry.getServiceRegistration(serviceType, host);
560 if (reg == null) {
561 throw new NotFoundException();
562 } else {
563 return new JaxbServiceRegistrationList(new JaxbServiceRegistration(reg));
564 }
565 } else if (isBlank(serviceType) && isBlank(host)) {
566
567 for (ServiceRegistration reg : serviceRegistry.getServiceRegistrations()) {
568 registrations.add(new JaxbServiceRegistration(reg));
569 }
570 } else if (isNotBlank(serviceType)) {
571
572 for (ServiceRegistration reg : serviceRegistry.getServiceRegistrationsByType(serviceType)) {
573 registrations.add(new JaxbServiceRegistration(reg));
574 }
575 } else if (isNotBlank(host)) {
576
577 for (ServiceRegistration reg : serviceRegistry.getServiceRegistrationsByHost(host)) {
578 registrations.add(new JaxbServiceRegistration(reg));
579 }
580 }
581 return registrations;
582 } catch (ServiceRegistryException e) {
583 throw new WebApplicationException(e);
584 }
585 }
586
587 @GET
588 @Path("services.json")
589 @Produces(MediaType.APPLICATION_JSON)
590 @RestQuery(
591 name = "servicesasjson",
592 description = "Returns a service registraton or list of available service registrations as JSON.",
593 returnDescription = "The services list as XML",
594 restParameters = {
595 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING,
596 description = "The service type identifier"),
597 @RestParameter(name = "host", isRequired = false, type = Type.STRING,
598 description = "The host, including the http(s) protocol")
599 },
600 responses = {
601 @RestResponse(responseCode = SC_OK, description = "Returned the available service."),
602 @RestResponse(responseCode = SC_NOT_FOUND,
603 description = "No service of that type on that host is registered.")
604 })
605 public JaxbServiceRegistrationList getRegistrationsAsJson(@QueryParam("serviceType") String serviceType,
606 @QueryParam("host") String host) throws NotFoundException {
607 return getRegistrationsAsXml(serviceType, host);
608 }
609
610 @GET
611 @Path("hosts.xml")
612 @Produces(MediaType.TEXT_XML)
613 @RestQuery(
614 name = "hostsasxml",
615 description = "Returns a host registraton or list of available host registrations as XML.",
616 returnDescription = "The host list as XML",
617 responses = {
618 @RestResponse(responseCode = SC_OK, description = "Returned the available hosts.")
619 })
620 public JaxbHostRegistrationList getHostsAsXml() throws NotFoundException {
621 JaxbHostRegistrationList registrations = new JaxbHostRegistrationList();
622 try {
623 for (HostRegistration reg : serviceRegistry.getHostRegistrations()) {
624 registrations.add(new JaxbHostRegistration(reg));
625 }
626 return registrations;
627 } catch (ServiceRegistryException e) {
628 throw new WebApplicationException(e);
629 }
630 }
631
632 @GET
633 @Path("hosts.json")
634 @Produces(MediaType.APPLICATION_JSON)
635 @RestQuery(
636 name = "hostsasjson",
637 description = "Returns a host registraton or list of available host registrations as JSON.",
638 returnDescription = "The host list as JSON",
639 responses = {
640 @RestResponse(responseCode = SC_OK, description = "Returned the available hosts.")
641 })
642 public JaxbHostRegistrationList getHostsAsJson() throws NotFoundException {
643 return getHostsAsXml();
644 }
645
646 @POST
647 @Path("job")
648 @Produces(MediaType.TEXT_XML)
649 @RestQuery(
650 name = "createjob",
651 description = "Creates a new job.",
652 returnDescription = "An XML representation of the job.",
653 restParameters = {
654 @RestParameter(name = "jobType", isRequired = true, type = Type.STRING,
655 description = "The job type identifier"),
656 @RestParameter(name = "host", isRequired = true, type = Type.STRING,
657 description = "The creating host, including the http(s) protocol"),
658 @RestParameter(name = "operation", isRequired = true, type = Type.STRING,
659 description = "The operation this job should execute"),
660 @RestParameter(name = "payload", isRequired = false, type = Type.TEXT,
661 description = "The job type identifier"),
662 @RestParameter(name = "start", isRequired = false, type = Type.BOOLEAN,
663 description = "Whether the job should be queued for dispatch and execution"),
664 @RestParameter(name = "jobLoad", isRequired = false, type = Type.STRING,
665 description = "The load this job will incur on the system"),
666 @RestParameter(name = "arg", isRequired = false, type = Type.TEXT,
667 description = "An argument for the operation"),
668 @RestParameter(name = "arg", isRequired = false, type = Type.TEXT,
669 description = "An argument for the operation"),
670 @RestParameter(name = "arg", isRequired = false, type = Type.TEXT,
671 description = "An argument for the operation"),
672 @RestParameter(name = "arg", isRequired = false, type = Type.TEXT,
673 description = "An argument for the operation"),
674 @RestParameter(name = "arg", isRequired = false, type = Type.TEXT,
675 description = "An argument for the operation")
676 },
677 responses = {
678 @RestResponse(responseCode = SC_CREATED, description = "Job created."),
679 @RestResponse(responseCode = SC_BAD_REQUEST,
680 description = "The required parameters were not supplied, bad request.")
681 })
682 public Response createJob(@Context HttpServletRequest request) {
683 String[] argArray = request.getParameterValues("arg");
684 List<String> arguments = null;
685 if (argArray != null && argArray.length > 0) {
686 arguments = Arrays.asList(argArray);
687 }
688 String jobType = request.getParameter("jobType");
689 String operation = request.getParameter("operation");
690 String host = request.getParameter("host");
691 String payload = request.getParameter("payload");
692 boolean start = StringUtils.isBlank(request.getParameter("start"))
693 || Boolean.TRUE.toString().equalsIgnoreCase(request.getParameter("start"));
694 try {
695 Job job = null;
696 if (StringUtils.isNotBlank(request.getParameter("jobLoad"))) {
697 Float jobLoad = Float.parseFloat(request.getParameter("jobLoad"));
698 job = ((ServiceRegistryJpaImpl) serviceRegistry).createJob(host, jobType, operation, arguments, payload,
699 start, serviceRegistry.getCurrentJob(), jobLoad);
700 } else {
701 job = ((ServiceRegistryJpaImpl) serviceRegistry).createJob(host, jobType, operation, arguments, payload,
702 start, serviceRegistry.getCurrentJob());
703 }
704 return Response.created(job.getUri()).entity(new JaxbJob(job)).build();
705 } catch (IllegalArgumentException e) {
706 throw new WebApplicationException(Status.BAD_REQUEST);
707 } catch (Exception e) {
708 throw new WebApplicationException(e);
709 }
710 }
711
712 @PUT
713 @Path("job/{id}.xml")
714 @Produces(MediaType.TEXT_XML)
715 @RestQuery(
716 name = "updatejob",
717 description = "Updates an existing job",
718 returnDescription = "No content",
719 pathParameters = {
720 @RestParameter(name = "id", isRequired = true, type = Type.STRING, description = "The job identifier")
721 },
722 restParameters = {
723 @RestParameter(name = "job", isRequired = true, type = Type.TEXT, description = "The updated job as XML")
724 },
725 responses = {
726 @RestResponse(responseCode = SC_NO_CONTENT, description = "Job updated."),
727 @RestResponse(responseCode = SC_NOT_FOUND, description = "Job not found.")
728 })
729 public Response updateJob(@PathParam("id") String id, @FormParam("job") String jobXml) throws NotFoundException {
730 try {
731 Job job = JobParser.parseJob(jobXml);
732 serviceRegistry.updateJob(job);
733 return Response.status(Status.NO_CONTENT).build();
734 } catch (Exception e) {
735 throw new WebApplicationException(e);
736 }
737 }
738
739 @GET
740 @Path("job/{id}.xml")
741 @Produces(MediaType.TEXT_XML)
742 @RestQuery(
743 name = "jobasxml",
744 description = "Returns a job as XML.",
745 returnDescription = "The job as XML",
746 pathParameters = {
747 @RestParameter(name = "id", isRequired = true, type = Type.STRING, description = "The job identifier")
748 },
749 responses = {
750 @RestResponse(responseCode = SC_OK, description = "Job found."),
751 @RestResponse(responseCode = SC_NOT_FOUND, description = "No job with that identifier exists.")
752 })
753 public JaxbJob getJobAsXml(@PathParam("id") long id) throws NotFoundException {
754 return getJobAsJson(id);
755 }
756
757 @GET
758 @Path("job/{id}.json")
759 @Produces(MediaType.APPLICATION_JSON)
760 @RestQuery(
761 name = "jobasjson",
762 description = "Returns a job as JSON.",
763 returnDescription = "The job as JSON",
764 pathParameters = {
765 @RestParameter(name = "id", isRequired = true, type = Type.STRING, description = "The job identifier")
766 },
767 responses = {
768 @RestResponse(responseCode = SC_OK, description = "Job found."),
769 @RestResponse(responseCode = SC_NOT_FOUND, description = "No job with that identifier exists.")
770 })
771 public JaxbJob getJobAsJson(@PathParam("id") long id) throws NotFoundException {
772 try {
773 return new JaxbJob(serviceRegistry.getJob(id));
774 } catch (ServiceRegistryException e) {
775 throw new WebApplicationException(e);
776 }
777 }
778
779 @GET
780 @Path("job/{id}/children.xml")
781 @Produces(MediaType.TEXT_XML)
782 @RestQuery(
783 name = "childrenjobsasxml",
784 description = "Returns all children from a job as XML.",
785 returnDescription = "A list of children jobs as XML",
786 pathParameters = {
787 @RestParameter(name = "id", isRequired = true, type = Type.STRING, description = "The parent job identifier")
788 },
789 responses = {
790 @RestResponse(responseCode = SC_OK, description = "Jobs found.")
791 })
792 public JaxbJobList getChildrenJobsAsXml(@PathParam("id") long id) {
793 return getChildrenJobsAsJson(id);
794 }
795
796 @GET
797 @Path("job/{id}/children.json")
798 @Produces(MediaType.APPLICATION_JSON)
799 @RestQuery(
800 name = "childrenjobsasjson",
801 description = "Returns all children from a job as JSON.",
802 returnDescription = "A list of children jobs as JSON",
803 pathParameters = {
804 @RestParameter(name = "id", isRequired = true, type = Type.STRING, description = "The parent job identifier")
805 },
806 responses = {
807 @RestResponse(responseCode = SC_OK, description = "Jobs found.")
808 })
809 public JaxbJobList getChildrenJobsAsJson(@PathParam("id") long id) {
810 try {
811 return new JaxbJobList(serviceRegistry.getChildJobs(id));
812 } catch (ServiceRegistryException e) {
813 throw new WebApplicationException(e);
814 }
815 }
816
817 @GET
818 @Path("jobs.xml")
819 @Produces(MediaType.TEXT_XML)
820 public JaxbJobList getJobsAsXml(@QueryParam("serviceType") String serviceType,
821 @QueryParam("status") Job.Status status) {
822 try {
823 return new JaxbJobList(serviceRegistry.getJobs(serviceType, status));
824 } catch (ServiceRegistryException e) {
825 throw new WebApplicationException(e);
826 }
827
828 }
829
830 @GET
831 @Path("activeJobs.xml")
832 @Produces(MediaType.TEXT_XML)
833 @RestQuery(
834 name = "activejobsasxml",
835 description = "Returns all active jobs as XML.",
836 returnDescription = "A list of active jobs as XML",
837 responses = {
838 @RestResponse(responseCode = SC_OK, description = "Active jobs found.")
839 })
840 public JaxbJobList getActiveJobsAsXml() {
841 try {
842 return new JaxbJobList(serviceRegistry.getActiveJobs());
843 } catch (ServiceRegistryException e) {
844 throw new WebApplicationException(e);
845 }
846 }
847
848 @GET
849 @Path("activeJobs.json")
850 @Produces(MediaType.APPLICATION_JSON)
851 @RestQuery(
852 name = "activejobsasjson",
853 description = "Returns all active jobs as JSON.",
854 returnDescription = "A list of active jobs as JSON",
855 responses = {
856 @RestResponse(responseCode = SC_OK, description = "Active jobs found.")
857 })
858 public JaxbJobList getActiveJobsAsJson() {
859 try {
860 return new JaxbJobList(serviceRegistry.getActiveJobs());
861 } catch (ServiceRegistryException e) {
862 throw new WebApplicationException(e);
863 }
864 }
865
866 @GET
867 @Path("count")
868 @Produces(MediaType.TEXT_PLAIN)
869 @RestQuery(
870 name = "count",
871 description = "Returns the number of jobs matching the query parameters as plain text.",
872 returnDescription = "The number of matching jobs",
873 restParameters = {
874 @RestParameter(name = "serviceType", isRequired = false, type = Type.STRING,
875 description = "The service type identifier"),
876 @RestParameter(name = "status", isRequired = false, type = Type.STRING, description = "The job status"),
877 @RestParameter(name = "host", isRequired = false, type = Type.STRING,
878 description = "The host executing the job"),
879 @RestParameter(name = "operation", isRequired = false, type = Type.STRING,
880 description = "The job's operation")
881 },
882 responses = {
883 @RestResponse(responseCode = SC_OK, description = "Job count returned.")
884 })
885 public long count(@QueryParam("serviceType") String serviceType, @QueryParam("status") Job.Status status,
886 @QueryParam("host") String host, @QueryParam("operation") String operation) {
887 try {
888 if (isNotBlank(host) && isNotBlank(operation)) {
889 if (isBlank(serviceType)) {
890 throw new WebApplicationException(Response.serverError().entity("Service type must not be null").build());
891 }
892 return serviceRegistry.count(serviceType, host, operation, status);
893 } else if (isNotBlank(host)) {
894 if (isBlank(serviceType)) {
895 throw new WebApplicationException(Response.serverError().entity("Service type must not be null").build());
896 }
897 return serviceRegistry.countByHost(serviceType, host, status);
898 } else if (isNotBlank(operation)) {
899 if (isBlank(serviceType)) {
900 throw new WebApplicationException(Response.serverError().entity("Service type must not be null").build());
901 }
902 return serviceRegistry.countByOperation(serviceType, operation, status);
903 } else {
904 return serviceRegistry.count(serviceType, status);
905 }
906 } catch (ServiceRegistryException e) {
907 throw new WebApplicationException(e);
908 }
909 }
910
911 @GET
912 @Path("maxconcurrentjobs")
913 @Produces(MediaType.TEXT_PLAIN)
914 @RestQuery(
915 name = "maxconcurrentjobs",
916 description = "Returns the number of jobs that the servers in this service registry can execute concurrently. "
917 + "If there is only one server in this service registry this will be the number of jobs that one server is "
918 + "able to do at one time. If it is a distributed install across many servers then this number will be the "
919 + "total number of jobs the cluster can process concurrently.",
920 returnDescription = "The maximum number of concurrent jobs",
921 responses = {
922 @RestResponse(responseCode = SC_OK, description = "Maximum number of concurrent jobs returned.")
923 })
924 @Deprecated
925 public Response getMaximumConcurrentWorkflows() {
926 return Response.status(Status.MOVED_PERMANENTLY).type(MediaType.TEXT_PLAIN)
927 .header("Location", servicePath + "/maxload").build();
928 }
929
930 @GET
931 @Path("maxload")
932 @Produces(MediaType.TEXT_XML)
933 @RestQuery(
934 name = "maxload",
935 description = "Returns the maximum load that servers in this service registry can execute concurrently. "
936 + "If there is only one server in this service registry this will be the maximum load that one server is "
937 + "able to handle at one time. If it is a distributed install across many servers then this number will be "
938 + "the maximum load the cluster can process concurrently.",
939 returnDescription = "The maximum load of the cluster or server",
940 restParameters = {
941 @RestParameter(name = "host", isRequired = false, type = Type.STRING,
942 description = "The host you want to know the maximum load for.")
943 },
944 responses = {
945 @RestResponse(responseCode = SC_OK, description = "Maximum load for the cluster.")
946 })
947 public Response getMaxLoadOnNode(@QueryParam("host") String host) throws NotFoundException {
948 try {
949 if (StringUtils.isEmpty(host)) {
950 return Response.ok(serviceRegistry.getMaxLoads()).build();
951 } else {
952 SystemLoad systemLoad = new SystemLoad();
953 systemLoad.addNodeLoad(serviceRegistry.getMaxLoadOnNode(host));
954 return Response.ok(systemLoad).build();
955 }
956 } catch (ServiceRegistryException e) {
957 throw new WebApplicationException(e);
958 }
959 }
960
961 @GET
962 @Path("currentload")
963 @Produces(MediaType.TEXT_XML)
964 @RestQuery(
965 name = "currentload",
966 description = "Returns the current load on the servers in this service registry. "
967 + "If there is only one server in this service registry this will be the the load that one server. "
968 + "If it is a distributed install across many servers then this number will be a dictionary of the load on "
969 + "all nodes in the cluster.",
970 returnDescription = "The current load across the cluster",
971 restParameters = {},
972 responses = {
973 @RestResponse(responseCode = SC_OK, description = "Current load for the cluster.")
974 })
975 public Response getCurrentLoad() {
976 try {
977 return Response.ok(serviceRegistry.getCurrentHostLoads()).build();
978 } catch (ServiceRegistryException e) {
979 throw new WebApplicationException(e);
980 }
981 }
982
983 @GET
984 @Path("ownload")
985 @Produces(MediaType.TEXT_PLAIN)
986 @RestQuery(
987 name = "ownload",
988 description = "Returns the current load on this service registry's node.",
989 returnDescription = "The current load across the cluster",
990 restParameters = {},
991 responses = {
992 @RestResponse(responseCode = SC_OK, description = "Current load for the cluster.")
993 })
994 public Response getOwnLoad() {
995 try {
996 return Response.ok(serviceRegistry.getOwnLoad()).build();
997 } catch (ServiceRegistryException e) {
998 throw new WebApplicationException(e);
999 }
1000 }
1001
1002
1003 @DELETE
1004 @Path("job/{id}")
1005 @RestQuery(
1006 name = "deletejob",
1007 description = "Deletes a job from the service registry",
1008 returnDescription = "No data is returned, just the HTTP status code",
1009 pathParameters = {
1010 @RestParameter(isRequired = true, name = "id", type = Type.INTEGER, description = "ID of the job to delete")
1011 },
1012 responses = {
1013 @RestResponse(responseCode = SC_NO_CONTENT, description = "Job successfully deleted"),
1014 @RestResponse(responseCode = SC_NOT_FOUND, description = "Job with given id could not be found")
1015 })
1016 public Response deleteJob(@PathParam("id") long id) throws NotFoundException {
1017 try {
1018 serviceRegistry.removeJobs(Collections.singletonList(id));
1019 return Response.noContent().build();
1020 } catch (ServiceRegistryException e) {
1021 throw new WebApplicationException(e);
1022 }
1023 }
1024
1025
1026 @POST
1027 @Path("removejobs")
1028 @RestQuery(
1029 name = "removejobs",
1030 description = "Removes all given jobs and their child jobs",
1031 returnDescription = "No data is returned, just the HTTP status code",
1032 restParameters = {
1033 @RestParameter(name = "jobIds", isRequired = true, description = "The IDs of the jobs to delete",
1034 type = Type.TEXT),
1035 },
1036 responses = {
1037 @RestResponse(responseCode = SC_NO_CONTENT, description = "Jobs successfully removed"),
1038 @RestResponse(responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1039 description = "Error while removing jobs")
1040 })
1041 public Response removeParentlessJobs(@FormParam("jobIds") String jobIds) throws NotFoundException {
1042 try {
1043 final JSONArray array = (JSONArray) JSONValue.parse(jobIds);
1044 final List<Long> jobIdList = Arrays.asList((Long[]) array.toArray(new Long[0]));
1045 serviceRegistry.removeJobs(jobIdList);
1046 return Response.noContent().build();
1047 } catch (ServiceRegistryException e) {
1048 throw new WebApplicationException(e);
1049 }
1050 }
1051
1052
1053 @POST
1054 @Path("removeparentlessjobs")
1055 @RestQuery(
1056 name = "removeparentlessjobs",
1057 description = "Removes all jobs without a parent job which have passed their lifetime",
1058 returnDescription = "No data is returned, just the HTTP status code",
1059 restParameters = {
1060 @RestParameter(name = "lifetime", isRequired = true, type = Type.INTEGER,
1061 description = "Lifetime of parentless jobs")
1062 },
1063 responses = {
1064 @RestResponse(responseCode = SC_NO_CONTENT, description = "Parentless jobs successfully removed"),
1065 @RestResponse(responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1066 description = "Error while removing parentless jobs")
1067 })
1068 public Response removeParentlessJobs(@FormParam("lifetime") int lifetime) {
1069 try {
1070 serviceRegistry.removeParentlessJobs(lifetime);
1071 return Response.noContent().build();
1072 } catch (ServiceRegistryException e) {
1073 throw new WebApplicationException(e);
1074 }
1075 }
1076
1077 }