Sunday, September 16, 2012

Declarative Services with Karaf Part 3: Managed Services

Continuing my series on Declarative Services in Karaf in this article we will take a look at Managed Services and how they behave when using the Service Component Runtime (SCR) in Karaf. Managed Services are OSGi services that need access to configuration data at initialization and/or run-time. These configurations are administered through the OSGi Configuration Admin service which is a core service in Karaf. Karaf provides three ways to manage configurations used by the Config Admin service: the Felix File Install framework, the Config Admin CLI Commands and the Web Console.

The Felix File Install framework is configured to monitor the $KARAF_HOME/etc directory for bundle configuration files. When files are added with a name that matches the Managed Service PID and ending with .cfg, the file is read and a new configuration is added to the Config Admin registry.

Lets take a look at the second example that was included in the code we downloaded from Part 2: managed-service.

The managed-service project it is basically the same as what we found in the service project from Part 2. We have a delayed component, the ManagedGreeterService, and an immediate component, the ManagedGreeterComponent.

The ManagedGreeterComponent is mostly the same as what was in the GreeterComponent from Part 2. The only real difference is that it calls the new interface from the activate and deactivate methods. The annotations have not changed though so lets take a look at our delayed component.

Since we are discussing a managed service it makes sense to create a service that can demonstrate how creating, changing and removing the configuration for a given managed service behaves when using DS. To explore this we have created a new interface that allows a consumer of the delayed component to stop and start its logic:

ManagedGreeterService.java
public interface ManagedGreeterService {
    void startGreeter();
    void stopGreeter();
}

The ManagedGreeterServiceImpl.java code has a number of changes that are worth pointing out. First, in lines 47-77 we have added a runnable that when started, will print to our log until the stop method is called. This runnable requires configuration before it is started though. That is going to be handled by the managed part of our delayed component.

First look at the activate method on line 17. The signature has changed by adding a Map argument (one of the three possible arguments you can pass to the any of the lifecycle methods in DS, see the spec for further details). The SCR will recognize this signature and will ask the ConfigAdmin service for a configuration that corresponds to the components name, in this case, the ManagedGreeterService.

Remember that a delayed component will activate when all of its dependencies are satisfied and that there is an active reference to it. In this case the dependency is not another service but the configuration. The SCR annotations though configure our components configuration to be optional by default though. Therefore we need to tell the SCR that a configuration is required and to do that we set the annotation attribute configurationPolicy to ConfigurationPolicy.require on line 2. This will make a configuration required for our component before it will become satisfied.

ManagedGreeterServiceImpl.java
@Component(name = ManagedGreeterServiceImpl.COMPONENT_NAME,
           configurationPolicy = ConfigurationPolicy.require)
public class ManagedGreeterServiceImpl implements ManagedGreeterService {

    public static final String COMPONENT_NAME = "ManagedGreeterService";
    public static final String COMPONENT_LABEL = "Managed Greeeter Service";

    private static final Logger LOG = LoggerFactory.getLogger(ManagedGreeterServiceImpl.class);

    private ExecutorService executor = Executors.newCachedThreadPool();
    private Worker worker = new Worker();

    /**
     * Called when all of the SCR Components required dependencies have been
     * satisfied.
     */
    @Activate
    public void activate(final Map<string> properties) {
        LOG.info("Activating the " + COMPONENT_LABEL);

        if (properties.containsKey("salutation"))
            worker.setSalutation((String)properties.get("salutation"));

        if (properties.containsKey("name"))
            worker.setName((String)properties.get("name"));
    }

    /**
     * Called when any of the SCR Components required dependencies become
     * unsatisfied.
     */
    @Deactivate
    public void deactivate() {
        LOG.info("Deactivating the " + COMPONENT_LABEL);
    }

    public void startGreeter() {
        executor.execute(worker);
    }

    public void stopGreeter() {
        if (!executor.isTerminated()) {
            executor.shutdownNow();
        }
    }

    /**
     * Thread worker that continuously prints a message.
     */
    private class Worker implements Runnable {

        private String name;
        private String salutation;

        public void run() {
            boolean running = true;
            int messageCount = 0;
            while (running) {
                try {
                    LOG.info("Message " + (++messageCount) + ": " + salutation + " " + name);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    running = false;
                    LOG.info("Thread shutting down");
                }
            }
        }

        public void setName(String userName) {
            this.name = userName;
        }

        public void setSalutation(String salutation) {
            this.salutation = salutation;
        }
    }
}

Running the Code
Lets see how our new code behaves in Karaf using the SCR commands. First lets install it:

install -s mvn:org.apache.karaf.scr/org.apache.karaf.scr.examples.managed.service/2.2.9

To verify it was installed run scr:list from the Karaf CLI.
scr:list 
   ID   State             Component Name
[7   ] [UNSATISFIED     ] ManagedGreeterComponent
[8   ] [UNSATISFIED     ] ManagedGreeterService

Both components should be UNSATISFIED because we have not created a configuration yet for the ManagedGreeterService. Lets create one then and see what happens. From the Karaf CLI issue the following configuration commands:
config:edit ManagedGreeterService
config:propset salutation "Hello"
config:propset name "Scott"
config:update
Now when we use the scr:list command we see that our components are now SATISFIED
scr:list 
   ID   State             Component Name
[9   ] [ACTIVE          ] ManagedGreeterComponent
[8   ] [ACTIVE          ] ManagedGreeterService
And if we look into our log file we will start to see the output from our ManagedGreeterService:
INFO  | vent: pid=ManagedGreeterService) | Activating the Managed Greeeter Service
INFO  | vent: pid=ManagedGreeterService) | Activating the Managed Greeter Component
INFO  | pool-19-thread-1                 | Message 1: Hello Scott
INFO  | pool-19-thread-1                 | Message 2: Hello Scott
INFO  | opt/karaf/apache-karaf-2.2.9/etc | Installed /opt/karaf/apache-karaf-2.2.9/etc/ManagedGreeterService.cfg
INFO  | pool-19-thread-1                 | Message 3: Hello Scott
INFO  | pool-19-thread-1                 | Message 4: Hello Scott
INFO  | pool-19-thread-1                 | Message 5: Hello Scott
...
Note: Observe on line 5 that when we use the CLI to add a configuration a ManagedGreeterService.cfg file is added to the etc directory.

Lets remove the configuration now and verify that everything shuts down and is cleaned up:
config:delete ManagedGreeterService
Using the scr:list command again we see that our components have become UNSATISFIED and have deactivated. Reviewing the log file verifies this.
INFO  | vent: pid=ManagedGreeterService) | Deactivating the Managed Greeter Component
INFO  | pool-19-thread-1                 | Thread shutting down
INFO  | vent: pid=ManagedGreeterService) | Deactivating the Managed Greeeter Service
INFO  | opt/karaf/apache-karaf-2.2.9/etc | Uninstalled /opt/karaf/apache-karaf-2.2.9/etc/ManagedGreeterService.cfg
Both services are shut down and the CFG is removed.

Updating at Run-Time
Now what happens when we change the configuration without shutting the components down? To find out lets update it. Issue the following command on the Karaf CLI:
config:edit ManagedGreeterService
config:propset salutation "Bonjour"
config:propset name "Sully"
config:update
Now lets go back and update the configuration with new values:
config:edit ManagedGreeterService
config:propset salutation "Bonjour"
config:propset name "Sully"
config:update
If we take a look at the log file we see that the components behaved in a very predictable manor. They were activated upon the initial configuration and then when the configuration changed the SCR was notified of the change and then proceeded to deactivate the ManagedGreeterComponent so it could deactivate and then reactivate the ManagedGreeterService with the new configuration. When completed the SCR called the activate on the ManagedGreeterComponent and we were off and running again.
INFO  | vent: pid=ManagedGreeterService) | Activating the Managed Greeeter Service
INFO  | vent: pid=ManagedGreeterService) | Activating the Managed Greeter Component
INFO  | pool-20-thread-1                 | Message 1: Hello Scott
INFO  | pool-20-thread-1                 | Message 2: Hello Scott
INFO  | opt/karaf/apache-karaf-2.2.9/etc | Installed /opt/karaf/apache-karaf-2.2.9/etc/ManagedGreeterService.cfg
INFO  | pool-20-thread-1                 | Message 3: Hello Scott
INFO  | pool-20-thread-1                 | Message 4: Hello Scott
INFO  | pool-20-thread-1                 | Message 5: Hello Scott
INFO  | vent: pid=ManagedGreeterService) | Deactivating the Managed Greeter Component
INFO  | pool-20-thread-1                 | Thread shutting down
INFO  | vent: pid=ManagedGreeterService) | Deactivating the Managed Greeeter Service
INFO  | vent: pid=ManagedGreeterService) | Activating the Managed Greeeter Service
INFO  | vent: pid=ManagedGreeterService) | Activating the Managed Greeter Component
INFO  | pool-21-thread-1                 | Message 1: Bonjour Sully
INFO  | pool-21-thread-1                 | Message 2: Bonjour Sully
INFO  | pool-21-thread-1                 | Message 3: Bonjour Sully
INFO  | pool-21-thread-1                 | Message 4: Bonjour Sully
INFO  | pool-21-thread-1                 | Message 5: Bonjour Sully

This cascading behavior of available dependencies allows our components to start and stop in a very predictable manor.

The Modified Life-cycle
In DS we have another lifecycle method that allows us to update the configuration of a component dynamically. It is referred to the "modified" life-cycle and we use the @Modified annotation to denote the method we choose to handle the modification. In our case we use the modified(Map properties) signature so the SCR knows to pass in the updated properties from the ConfigAdmin service. Here is the new code:
@Modified
public void modified(final Map<string> properties) {
    LOG.info("Modifying the " + COMPONENT_LABEL);

    // This time we really only need to make sure if it changed it isn't
    // empty
    if (properties.containsKey("salutation") && !properties.get("salutation").equals("")) {
        worker.setSalutation((String)properties.get("salutation"));
    }

    // Same for name
    if (properties.containsKey("name") && !properties.get("name").equals("")) {
        worker.setName((String)properties.get("name"));
    }
}
Before testing it out lets clean up our old configuration by issuing the config:delete ManagedGreeterService command at the Karaf CLI. Now if you are using the supplied example code you can uncomment the modified method, rebuild it and issue the update command on the Karaf CLI for the managed services bundle. Once that is done lets go back and issue the two different configuration update commands:
config:edit ManagedGreeterService
config:propset salutation "Bonjour"
config:propset name "Sully"
config:update
And then update the configuration with new values again:
config:edit ManagedGreeterService
config:propset salutation "Bonjour"
config:propset name "Sully"
config:update
Now lets take a look at our log to see how the components behaved:
INFO  | vent: pid=ManagedGreeterService) | Activating the Managed Greeeter Service
INFO  | vent: pid=ManagedGreeterService) | Activating the Managed Greeter Component
INFO  | pool-22-thread-1                 | Message 1: Hello Scott
INFO  | pool-22-thread-1                 | Message 2: Hello Scott
INFO  | opt/karaf/apache-karaf-2.2.9/etc | Installed /opt/karaf/apache-karaf-2.2.9/etc/ManagedGreeterService.cfg
INFO  | pool-22-thread-1                 | Message 3: Hello Scott
INFO  | pool-22-thread-1                 | Message 4: Hello Scott
INFO  | pool-22-thread-1                 | Message 5: Hello Scott
...
INFO  | vent: pid=ManagedGreeterService) | Modifying the Managed Greeeter Service
INFO  | pool-22-thread-1                 | Message 11: Bonjour Sully
INFO  | pool-22-thread-1                 | Message 12: Bonjour Sully
INFO  | pool-22-thread-1                 | Message 13: Bonjour Sully
INFO  | pool-22-thread-1                 | Message 14: Bonjour Sully
INFO  | pool-22-thread-1                 | Message 15: Bonjour Sully
Now this output should cause concern. Why? Because our components were never deactivated and threads were updated when the component was modified. We are no longer thread safe!

So what happened? Before when we only had the activate & deactivate method defined the activate & deactivate life-cycles were used to manage all updates of the component. This allowed our components to de/activate cleanly. When the component is configured with the modified life-cycle the SCR will only call the modified method which never causes the ManagedGreeterService to become UNSATISFIED. Therefore any component with a dependency on the ManagedGreeterService is never notified that there has been a change in the state of the component and they continue upon their merry way oblivious to the changes.
WRT thread safety, the same actually goes for our set/unset methods as well in the ManagedGreeterComponent. These methods along with the life-cycle methods are called by separate threads in the SCR. If you remember back in Part 1 in my Editorial Opinion, I touched on the importance of creating bind/unbind methods to allow our components to lock be thread safe. If you look at the ManagedGreeterComponent source code you will see where we make sure to wrap our service in lock blocks to ensure that it maintain thread safety.

That should give you a pretty good overview of Managed Services using DS in Karaf. In Part 4 of this series, we will take a look at using components to configure components use DS Factory components.

Next Article:
Part 4: Component Factories

Previous Articles:
Part 1: Getting Started
Part 2: The Basics

References:
Karaf
Felix File Install
OSGi 4.2 Specification
BND Annotations

1 comment:

  1. Hi Scott, I was re-reading your article and I always find it very informative!
    I have a small bug report and a question:

    (trivial) bug: in "Updating at Run-Time" both the `prop-set` are setting salutation in French, while I think the first one should be in English

    question:
    shouldn't ManagedGreeterServiceImpl explicitly invoke startGreeter() and stopGreeter() ?
    and if not, what should be condidered a "default" deactivation behavior, like in this case when I annotate a method that doesn't perform any state alteration?

    ReplyDelete