Skip to content

Creating a simulation, part 6. simulate adaptation

vitorsouza edited this page Nov 14, 2012 · 3 revisions

We've finally arrived at the last step of this tutorial on creating Zanshin simulations. If you've followed us so far, you should by now have a simulation that registers your system (in our case, the Meeting Scheduler) in the framework and sends logging information about a couple of scenarios that trigger AwReq failures according to the system's requirements models. Zanshin responds with some adaptation instructions, but our simulated target system controller ignores them.

In this last step, we will deal with this response, thus simulating the adaptation part of the feedback loop. First, however, let's talk about the concept of a simulated target system controller, to understand how to implement this.

The simulated target system controller

If we think of a real system using Zanshin to adapt itself, what we conclude is that this system (e.g., the Meeting Scheduler) should have a controller component responsible for interfacing with Zanshin: both sending logging information about users pursuing, succeeding and failing requirements (through some kind of probing infrastructure or code instrumentation) and reacting to adaptation actions received from the framework.

Think of an implementation of the Meeting Scheduler using Java EE as architecture with a web interface. When the user loads the form used to characterize a new meeting that should be scheduled, some probe should tell the target system controller that a user has started the task Characterize meeting, which in turn sends to Zanshin logging information on the start of T_CharactMeet for a specific user session.

Analogously, if, for some reason, there is an exception in the processing of this task, a probe should tell the controller that in the same user session, Characterize meeting has failed, which in turn sends to Zanshin the appropriate logging information (like in the simulation shown [previously](Creating a simulation, part 5. Simulate monitoring)). The framework then responds to the target system controller that the user should retry the task, which most likely is translated into a redirection to an informational page explaining that something went wrong, but the user should click a link and retry sending the form.

When doing simulations, however, we don't need to implement such a complicated controller. Instead, we implement a simulated controller, which so far just prints at the Simulation Console's log that an adaptation instruction has been received from Zanshin. We've seen that already in [part 5](Creating a simulation, part 5. Simulate monitoring) of the tutorial.

However, we should at least simulate the behavior of an actual target system controller, by having the simulation wait for the expected response from Zanshin and continue with the next part only after the framework responds to the problem caused by the first one. In the Meeting Scheduler first simulation, for example, when the task fails, the simulation waits for a response. When the response comes, the next part is ran and maybe the user retries the task but it fails again. If that would happen three times, for example, at the third time Zanshin's response would no longer be to retry, but to abort altogether, because the retry strategy has been associated with a max executions applicability condition, with value 2 (see the construction of the model, at [part 2](Creating a simulation, part 2. The model)).

This is where the lock object, introduced in [part 3](Creating a simulation, part 3. A simple Simulation class), comes to use. So far, all simulation parts that we have created have responded false in the method shouldWait(). When we change that to true, the Simulation Manager will put the simulation to wait in its lock object. The same lock object was given to the simulated target system controller created in [part 4](Creating a simulation, part 4. Simulation initialization), so when the controller receives the appropriate response from Zanshin, it can "wake the simulation up", and it will continue with the next part.

Now the question is: what exactly is the appropriate response? You can find that out by analyzing the code of each adaptation strategy in Table 3.6 (page 84) of Vítor's thesis. However, for our simulations, we will answer that question by checking the logs produced when they were ran [earlier](Creating a simulation, part 5. Simulate monitoring), as you will see next.

Completing the first simulation ( AR1 and task Characterize meeting )

If you go back to [part 5](Creating a simulation, part 5. Simulate monitoring) and analyze the Simulation Console logs, you will see that the retry strategy, which is the response of the framework for AR1's failure, consists of the following operations:

INFO: Simulated target system received EvoReq operation from Zanshin: copy-data(T_CharactMeet, T_CharactMeet) [session: 1,352,905,022,110]
INFO: Simulated target system received EvoReq operation from Zanshin: terminate(T_CharactMeet) [session: 1,352,905,022,110]
INFO: Simulated target system received EvoReq operation from Zanshin: rollback(T_CharactMeet) [session: 1,352,905,022,110]
INFO: Simulated target system received EvoReq operation from Zanshin: wait(5,000) [session: 1,352,905,022,110]
INFO: Simulated target system received EvoReq operation from Zanshin: initiate(T_CharactMeet) [session: 1,352,905,022,110]

Therefore, in the case of the first simulation, the "appropriate response" from Zanshin that should be changed is the one regarding the initiate() method. Going back to the SchedulerSimulatedTargetSystem, we should override this method in order not just to print something in the log like above, but also to notify any threads (simulations) waiting in the lock object that they should "wake up" and continue their business. This is done as below:

package it.unitn.disi.zanshin.simulation.cases.scheduler;

import it.unitn.disi.zanshin.simulation.SimulatedTargetSystem;

public class SchedulerSimulatedTargetSystem extends SimulatedTargetSystem {
	/** The object in which the simulation thread is sleeping, and therefore used to "wake it up". */
	private Object lock;

	/** Constructor. */
	public SchedulerSimulatedTargetSystem(Object lock) {
		this.lock = lock;
	}

	/** @see it.unitn.disi.zanshin.simulation.SimulatedTargetSystem#initiate(java.lang.Long, java.lang.String) */
	@Override
	public void initiate(Long sessionId, String reqName) {
		super.initiate(sessionId, reqName);

		// Initiate is the last command of the Retry strategy, thus we should notify the simulation to continue, as this
		// strategy is selected to deal with the failures of AR1.
		synchronized (lock) {
			lock.notifyAll();
		}
	}

	/** @see it.unitn.disi.zanshin.simulation.SimulatedTargetSystem#abort(java.lang.Long, java.lang.String) */
	@Override
	public void abort(Long sessionId, String awreqName) {
		super.abort(sessionId, awreqName);
		
		// After two attempts of the Retry strategy, Zanshin replies with an Abort, also for the AR1 simulation.
		synchronized (lock) {
			lock.notifyAll();
		}
	}
}

Note that super.initiate() is called so the log message will still be printed, then we notify any waiting threads in the lock object via lock.notifyAll(). The synchronized block is necessary because if the simulation is still busy (for instance, doing any post-processing after logging information has been sent to Zanshin ), the controller will wait until the simulation relinquishes the lock object, which will happen exactly when the Simulation Manager puts the simulation on hold.

The abort() method was also overridden because we will change our simulation to try and fail the task Characterize meeting three times. Two times Zanshin will attempt the Retry strategy, but at the third time that strategy will no longer be applicable, so it will abort. The simulation needs to be notified of the abort in order to finish with its fourth part, which basically just prints a message to the log. The new version of the SchedulerAR1FailureSimulation simulation class is provided in full below:

package it.unitn.disi.zanshin.simulation.cases.scheduler;

import it.unitn.disi.zanshin.simulation.Logger;
import it.unitn.disi.zanshin.simulation.cases.SimulationPart;

public class SchedulerAR1FailureSimulation extends AbstractSchedulerSimulation {
	/** The logger. */
	private static final Logger log = new Logger(SchedulerAR1FailureSimulation.class);

	/** @see it.unitn.disi.zanshin.simulation.cases.AbstractSimulation#doInit() */
	@Override
	protected void doInit() throws Exception {
		// Registers the Meeting Scheduler as target system in Zanshin.
		registerTargetSystem();
		
		// Adds the first part of the simulation to the list.
		parts.add(new SimulationPart() {
			@Override
			public boolean shouldWait() {
				return true;
			}
			
			@Override
			public void run() throws Exception {
				// Creates a user session, as if someone were using the Meeting Scheduler.
				sessionId = zanshin.createUserSession(targetSystemId);
				log.info("Created a new user session with id: {0}", sessionId); //$NON-NLS-1$

				// Simulates a failure in task "Characterize meeting".
				log.info("Meeting organizer tries to Characterize meeting but it fails!"); //$NON-NLS-1$
				zanshin.logRequirementStart(targetSystemId, sessionId, T_CHARACT_MEET);
				zanshin.logRequirementFailure(targetSystemId, sessionId, T_CHARACT_MEET);
			}
		});
		
		// Adds the second part of the simulation to the list.
		parts.add(new SimulationPart() {
			@Override
			public boolean shouldWait() {
				return true;
			}
			
			@Override
			public void run() throws Exception {
				// Simulates another failure in task "Characterize meeting".
				log.info("Meeting organizer tries *again* to Characterize meeting but it fails!"); //$NON-NLS-1$
				zanshin.logRequirementStart(targetSystemId, sessionId, T_CHARACT_MEET);
				zanshin.logRequirementFailure(targetSystemId, sessionId, T_CHARACT_MEET);
			}
		});
		
		// Adds the third part of the simulation to the list.
		parts.add(new SimulationPart() {
			@Override
			public boolean shouldWait() {
				return true;
			}
			
			@Override
			public void run() throws Exception {
				// Simulates another failure in task "Characterize meeting".
				log.info("Meeting organizer tries *one more time* to Characterize meeting but it fails!"); //$NON-NLS-1$
				zanshin.logRequirementStart(targetSystemId, sessionId, T_CHARACT_MEET);
				zanshin.logRequirementFailure(targetSystemId, sessionId, T_CHARACT_MEET);
			}
		});
		
		// Adds the fourth and last part of the simulation to the list (being the last one, it should not wait).
		parts.add(new SimulationPart() {
			@Override
			public boolean shouldWait() {
				return false;
			}
			
			@Override
			public void run() throws Exception {
				// Simulates another failure in task "Characterize meeting".
				log.info("The system aborts! Today is not a good day to schedule meetings..."); //$NON-NLS-1$
			}
		});
	}
}

Note that the three first parts of this simulation now have shouldWait() return true. Also note that the user session is created at the first part and the same session ID is used in the subsequent parts, as if the same user were trying to characterize a meeting. If you run this simulation, you should see something like the following at the Simulation Console (analyzing the Zanshin log is also interesting, but we will skip it this time):

INFO: Running simulation: Meeting Scheduler Simulation - AR1 Failure - "Characterize meeting", instance
INFO: Created a new user session with id: 1,352,913,771,698
INFO: Meeting organizer tries to Characterize meeting but it fails!
INFO: Simulated target system received EvoReq operation from Zanshin: copy-data(T_CharactMeet, T_CharactMeet) [session: 1,352,913,771,698]
INFO: Simulated target system received EvoReq operation from Zanshin: terminate(T_CharactMeet) [session: 1,352,913,771,698]
INFO: Simulated target system received EvoReq operation from Zanshin: rollback(T_CharactMeet) [session: 1,352,913,771,698]
INFO: Simulated target system received EvoReq operation from Zanshin: wait(5,000) [session: 1,352,913,771,698]
INFO: Simulated target system received EvoReq operation from Zanshin: initiate(T_CharactMeet) [session: 1,352,913,771,698]
INFO: Meeting organizer tries *again* to Characterize meeting but it fails!
INFO: Simulated target system received EvoReq operation from Zanshin: copy-data(T_CharactMeet, T_CharactMeet) [session: 1,352,913,771,698]
INFO: Simulated target system received EvoReq operation from Zanshin: terminate(T_CharactMeet) [session: 1,352,913,771,698]
INFO: Simulated target system received EvoReq operation from Zanshin: rollback(T_CharactMeet) [session: 1,352,913,771,698]
INFO: Simulated target system received EvoReq operation from Zanshin: wait(5,000) [session: 1,352,913,771,698]
INFO: Simulated target system received EvoReq operation from Zanshin: initiate(T_CharactMeet) [session: 1,352,913,771,698]
INFO: Meeting organizer tries *one more time* to Characterize meeting but it fails!
INFO: Simulated target system received EvoReq operation from Zanshin: abort(AR1) [session: 1,352,913,771,698]
INFO: The system aborts! Today is not a good day to schedule meetings...
INFO: (Meeting Scheduler Simulation - AR1 Failure - "Characterize meeting", instance) Simulation has finished (no more parts to run).

That's it for our first simulation! Now let's conclude also the second one.

Completing the second simulation ( AR4 and goal Find a suitable room )

Again analyzing the logs at [part 5](Creating a simulation, part 5. Simulate monitoring), we can see the following command when our second simulation runs:

INFO: Simulated target system received EvoReq operation from Zanshin: apply-config({CV_RfM=6.00000}) [from now on]

Therefore, we should override apply-config() in SchedulerSimulatedTargetSystem. Attention: there are two apply-config() methods that can be overridden. Choose the one without the Long argument, as that applies to reconfigurations at the instance level and here we are dealing with reconfigurations at the class level. Here's the overridden method:

	/** @see it.unitn.disi.zanshin.simulation.SimulatedTargetSystem#applyConfig(java.util.Map) */
	@Override
	public void applyConfig(Map<String, String> newConfig) {
		super.applyConfig(newConfig);
		
		// ApplyConfig is the last command of the Reconfiguration strategy, thus we should notify the simulation to
		// continue, as this strategy is selected to deal with failures of AR4.
		synchronized (lock) {
			lock.notifyAll();
		}		
	}

And here's the new version of SchedulerAR4FailureSimulation, also in full:

package it.unitn.disi.zanshin.simulation.cases.scheduler;

import it.unitn.disi.zanshin.simulation.Logger;
import it.unitn.disi.zanshin.simulation.cases.SimulationPart;

/**
 * TODO: document this type.
 *
 * @author Vitor E. Silva Souza (vitorsouza@gmail.com)
 * @version 1.0
 */
public class SchedulerAR4FailureSimulation extends AbstractSchedulerSimulation {
	/** The logger. */
	private static final Logger log = new Logger(SchedulerAR4FailureSimulation.class);

	/** @see it.unitn.disi.zanshin.simulation.cases.AbstractSimulation#doInit() */
	@Override
	protected void doInit() throws Exception {
		// Registers the Meeting Scheduler as target system in Zanshin.
		registerTargetSystem();
				
		// Adds the first part of the simulation to the list.
		parts.add(new SimulationPart() {
			@Override
			public boolean shouldWait() {
				return true;
			}
			
			@Override
			public void run() throws Exception {
				// Creates a user session, as if someone were using the A-CAD.
				sessionId = zanshin.createUserSession(targetSystemId);
				log.info("Created a new user session with id: {0}", sessionId); //$NON-NLS-1$

				// Simulates a failure of goal "Find a suitable room".
				log.info("No rooms available and both partner and hotels fail!"); //$NON-NLS-1$
				zanshin.logRequirementStart(targetSystemId, sessionId, D_LOCAL_AVAIL);
				zanshin.logRequirementFailure(targetSystemId, sessionId, D_LOCAL_AVAIL);
				zanshin.logRequirementStart(targetSystemId, sessionId, T_CALL_PARTNER);
				zanshin.logRequirementFailure(targetSystemId, sessionId, T_CALL_PARTNER);
				zanshin.logRequirementStart(targetSystemId, sessionId, T_CALL_HOTEL);
				zanshin.logRequirementFailure(targetSystemId, sessionId, T_CALL_HOTEL);
				
				// Ends the user session.
				zanshin.disposeUserSession(targetSystemId, sessionId);
			}
		});
		
		// Adds the second part of the simulation to the list.
		parts.add(new SimulationPart() {
			@Override
			public boolean shouldWait() {
				return true;
			}
			
			@Override
			public void run() throws Exception {
				// Creates a user session, as if someone were using the A-CAD.
				sessionId = zanshin.createUserSession(targetSystemId);
				log.info("Created a new user session with id: {0}", sessionId); //$NON-NLS-1$

				// Simulates a failure of goal "Find a suitable room".
				log.info("No rooms available and both partner and hotels fail!"); //$NON-NLS-1$
				zanshin.logRequirementStart(targetSystemId, sessionId, D_LOCAL_AVAIL);
				zanshin.logRequirementFailure(targetSystemId, sessionId, D_LOCAL_AVAIL);
				zanshin.logRequirementStart(targetSystemId, sessionId, T_CALL_PARTNER);
				zanshin.logRequirementFailure(targetSystemId, sessionId, T_CALL_PARTNER);
				zanshin.logRequirementStart(targetSystemId, sessionId, T_CALL_HOTEL);
				zanshin.logRequirementFailure(targetSystemId, sessionId, T_CALL_HOTEL);
				
				// Ends the user session.
				zanshin.disposeUserSession(targetSystemId, sessionId);
			}
		});
		
		// Adds the third and final part of the simulation to the list (being the last one, it should not wait).
		parts.add(new SimulationPart() {
			@Override
			public boolean shouldWait() {
				return false;
			}
			
			@Override
			public void run() throws Exception {
				// Creates a user session, as if someone were using the A-CAD.
				sessionId = zanshin.createUserSession(targetSystemId);
				log.info("Created a new user session with id: {0}", sessionId); //$NON-NLS-1$

				// Simulates the success of "Find a suitable room".
				log.info("This time, calling a partner institution succeeds!"); //$NON-NLS-1$
				zanshin.logRequirementStart(targetSystemId, sessionId, T_CALL_PARTNER);
				zanshin.logRequirementSuccess(targetSystemId, sessionId, T_CALL_PARTNER);
				
				// Ends the user session.
				zanshin.disposeUserSession(targetSystemId, sessionId);
			}
		});
	}
}

Note that, as before, only the last part of this simulation is a non-waiting part. The Manager, however, successfully notifies the simulation to continue at the appropriate times because of our simulated controller. This is the output of the Simulation Console:

INFO: Running simulation: Meeting Scheduler Simulation - AR4 Failure - "Find a suitable room", instance
INFO: Created a new user session with id: 1,352,914,391,952
INFO: No rooms available and both partner and hotels fail!
INFO: Simulated target system received EvoReq operation from Zanshin: apply-config({CV_RfM=6.00000}) [from now on]
INFO: Created a new user session with id: 1,352,914,391,971
INFO: No rooms available and both partner and hotels fail!
INFO: Simulated target system received EvoReq operation from Zanshin: apply-config({CV_RfM=7.00000}) [from now on]
INFO: Created a new user session with id: 1,352,914,391,979
INFO: This time, calling a partner institution succeeds!
INFO: (Meeting Scheduler Simulation - AR4 Failure - "Find a suitable room", instance) Simulation has finished (no more parts to run).

In the last part of the simulation, one of the children of the Find a suitable room goal succeeds, propagating such success up to the goal. This was done so you could notice at the Zanshin logs that the adaptation session created to deal with this class-scoped failure is closed after the problem is solved. You should see something like this:

[zanshin.adaptation.qualia] INFO: (Session: AR4 / 2012-11-14 18:36:08.035) Indicator AR4 has been evaluated to true
[zanshin.adaptation.qualia] INFO: (Session: AR4 / 2012-11-14 18:36:08.035) Evaluating resolution: true
[zanshin.adaptation       ] INFO: (Session: AR4 / 2012-11-14 18:36:08.035) The problem has been solved or there is nothing else to try. Adaptation session will be terminated.

Conclusions

In this tutorial, we have shown you how to create your own Zanshin simulations by explaining some of the main concepts of the framework and illustrating the process with the help of an example system, the Meeting Scheduler. Although this tutorial was long, once you build a few simulations you will understand better this process and building simulations will be very quick.

Because many aspects of a real target system controller are ignored when doing simulations, coupled with the fact that you can reuse the requirements models produced at the beginning of this tutorial when using Zanshin with a real system, we believe that running a few simulations before attempting to [apply the framework for real](How to use Zanshin with a real system) is highly advised.