Skip to content

Commit

Permalink
Merge pull request #178 from pdowler/master
Browse files Browse the repository at this point in the history
cadc-vosi: optional startupMode config
  • Loading branch information
pdowler authored Mar 26, 2024
2 parents 5dcab89 + 48dcef5 commit 1f5eff3
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 46 deletions.
40 changes: 40 additions & 0 deletions cadc-vosi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,59 @@ cadc-tap-schema</a> library.
In addition to the normal VOSI API, the VOSI-availability component supports optional
changing of the service state. Supported states: ReadWrite (normal), ReadOnly, and Offline.

Set state example:
```
curl --cert <cert file> -d state=ReadOnly https://example.net/srv/availability
```
Get example:
```
curl https://example.net/srv/availability
```

## cadc-vosi.properties
This config file (optional) allows deployers to specify users that are allowed to
change the service state via HTTP POST to the `/availability` endpoint.
```properties
user = {X509 distinguished name}
user = ...

# optional mode at startup: Offline, ReadOnly, ReadWrite (default)
startupMode = ReadWrite|ReadOnly|Offline
```

For the X509 distinguished name, the availability endpoint can successfully authorise the
user even when the associated AAI system is unavailable, so this mechanism is more robust
as it does not depend on any other functioning component.

If an optional _startupMode_ is configured, the availability servlet will create the service-specific
AvailabilityPlugin and call `setState`. The behaviour is completely in the control of the service
and how it implements the plugin.

## TODO
- rewrite the AvailabilityServlet as a RestAction based on the `cadc-rest` library
- add ability to configure users and groups consistent with the `cadc-log` library
- remove the old CapabilitiesServlet
- developer documentation (how to include in web.xml)

## temporary developer documentation
The following (or something like it) is needed in the `web.xml` deployment descriptor to add
an /avaailability endpoint to a service:
```xml
<!-- VOSI availability -->
<servlet>
<servlet-name>AvailabilityServlet</servlet-name>
<servlet-class>ca.nrc.cadc.vosi.AvailabilityServlet</servlet-class>
<init-param>
<param-name>ca.nrc.cadc.vosi.AvailabilityPlugin</param-name>
<param-value>fully.qualified.classname.for.AvailabilityPluginImpl</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>AvailabilityServlet</servlet-name>
<url-pattern>/availability</url-pattern>
</servlet-mapping>
```
Especially if using the _startupMode_, putting this servlet early in the load sequence (2 above, before
service specific endpoints and init) will ensure that the startup mode is set before other init happens.

4 changes: 2 additions & 2 deletions cadc-vosi/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ sourceCompatibility = 1.8

group = 'org.opencadc'

version = '1.4.5'
version = '1.4.6'

description = 'OpenCADC VOSI server library'
def git_url = 'https://github.com/opencadc/reg'
Expand All @@ -27,7 +27,7 @@ dependencies {
compile 'javax.servlet:javax.servlet-api:[3.1.0,)'

compile 'org.opencadc:cadc-util:[1.6,)'
compile 'org.opencadc:cadc-rest:[1.3.6,)'
compile 'org.opencadc:cadc-rest:[1.3.20,)'
compile 'org.opencadc:cadc-registry:[1.3.5,)'

runtime 'org.jdom:saxpath:1.0-FCS'
Expand Down
32 changes: 26 additions & 6 deletions cadc-vosi/src/main/java/ca/nrc/cadc/vosi/AvailabilityServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,12 @@
import ca.nrc.cadc.log.ServletLogInfo;
import ca.nrc.cadc.log.WebServiceLogInfo;
import ca.nrc.cadc.net.NetUtil;
import ca.nrc.cadc.util.InvalidConfigException;
import ca.nrc.cadc.util.MultiValuedProperties;
import ca.nrc.cadc.util.PropertiesReader;
import ca.nrc.cadc.util.StringUtil;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.security.Principal;
import java.util.HashSet;
import java.util.List;
Expand All @@ -103,6 +105,7 @@ public class AvailabilityServlet extends HttpServlet {
private static final long serialVersionUID = 201003131300L;

private static final String AVAILABILITY_PROPERTIES = "cadc-vosi.properties";
private static final String MODE_KEY = "startupMode";
private static final String USERS_PROPERTY = "user";

private String pluginClassName;
Expand All @@ -115,6 +118,27 @@ public void init(ServletConfig config)
this.appName = config.getServletContext().getContextPath().substring(1).replaceAll("/", "-");
this.pluginClassName = config.getInitParameter(AvailabilityPlugin.class.getName());
log.info("application: " + appName + " plugin impl: " + pluginClassName);

MultiValuedProperties mvp = getAvailabilityProperties();
String startupMode = mvp.getFirstPropertyValue(MODE_KEY);
if (startupMode != null) {
AvailabilityPlugin ap = loadPlugin();
ap.setState(startupMode);
}
}

private AvailabilityPlugin loadPlugin() throws InvalidConfigException {
try {
Class wsClass = Class.forName(pluginClassName);
AvailabilityPlugin ap = (AvailabilityPlugin) wsClass.getConstructor().newInstance();
ap.setAppName(appName);
log.debug("loaded: " + wsClass);
return ap;
} catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException
| InstantiationException | NoSuchMethodException | SecurityException
| InvocationTargetException ex) {
throw new InvalidConfigException("failed to load AvailabilityPlugin: " + pluginClassName, ex);
}
}

@Override
Expand All @@ -128,9 +152,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
logInfo.setSubject(subject);
log.info(logInfo.start());

Class wsClass = Class.forName(pluginClassName);
AvailabilityPlugin ap = (AvailabilityPlugin) wsClass.newInstance();
ap.setAppName(appName);
AvailabilityPlugin ap = loadPlugin();

String detail = request.getParameter("detail");
if (detail != null && detail.equals("min")) {
Expand Down Expand Up @@ -174,9 +196,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
logInfo.setSubject(subject);
log.info(logInfo.start());

Class wsClass = Class.forName(pluginClassName);
AvailabilityPlugin ap = (AvailabilityPlugin) wsClass.newInstance();
ap.setAppName(appName);
AvailabilityPlugin ap = loadPlugin();

IdentityManager im = AuthenticationUtil.getIdentityManager();
String caller = im.toDisplayString(subject);
Expand Down
41 changes: 3 additions & 38 deletions cadc-vosi/src/main/java/ca/nrc/cadc/vosi/CapInitAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import ca.nrc.cadc.reg.Capabilities;
import ca.nrc.cadc.reg.CapabilitiesReader;
import ca.nrc.cadc.rest.InitAction;
import ca.nrc.cadc.rest.Version;
import ca.nrc.cadc.util.StringUtil;

import java.io.StringReader;
Expand Down Expand Up @@ -189,7 +190,7 @@ public void doInit() {
}

try {
String version = findLibraryVersion(CapInitAction.class);
Version version = getLibraryVersion(CapInitAction.class);

jndiKey = componentID + ".version";
try {
Expand All @@ -198,46 +199,10 @@ public void doInit() {
} catch (NamingException e) {
log.debug("no previously bound value, continuting");
}
initContext.bind(jndiKey, version);
initContext.bind(jndiKey, version.getMajorMinor());
log.info("doInit: version=" + version + " stored via JNDI: " + jndiKey);
} catch (Exception ex) {
throw new IllegalArgumentException("CONFIG: failed to set version flag", ex);
}
}

// TODO: move this code up to cadc-rest or cadc-util
private String findLibraryVersion(Class probe) {
String ret = "no-version-found";
String rname = probe.getSimpleName() + ".class";
try {

URL resURL = probe.getResource(rname);
log.debug("library URL: " + resURL);
// assume assume maven-central naming conventions for jar
// jar:file:/path/to/{library}-{ver}.jar!/package/subpackage/{rname}
if (resURL != null) {
String[] parts = resURL.toExternalForm().split("[:!]");
int i = 0;
for (String p : parts) {
if (p.endsWith(".jar")) {
int s = p.lastIndexOf('/');
String ver = p.substring(s + 1); // {library}-{ver}.jar
ver = ver.replace(".jar", ""); // {library}-{ver}

// extract {major}.{minor} only
String[] mmp = ver.split("\\.");
if (mmp.length > 2) {
ret = mmp[0] + "." + mmp[1];
} else {
ret = ver;
}
}
}
}
} catch (Exception ex) {
log.error("failed to find version for " + rname + " from classpath", ex);
}

return ret;
}
}

0 comments on commit 1f5eff3

Please sign in to comment.