/* * Minha.pt: middleware testing platform. * Copyright (c) 2011-2014, Universidade do Minho. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package pt.minha.api.sim; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.List; import java.util.Properties; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import pt.minha.api.Entry; import pt.minha.api.Exit; import pt.minha.api.Host; import pt.minha.api.Main; import pt.minha.api.Milestone; import pt.minha.api.ContainerException; import pt.minha.api.World; import pt.minha.kernel.instrument.ClassConfig; import pt.minha.kernel.simulation.Resource; import pt.minha.kernel.simulation.Scheduler; import pt.minha.kernel.simulation.Timeline; import pt.minha.models.global.ResultHolder; import pt.minha.models.global.disk.Storage; import pt.minha.models.global.net.Network; import pt.minha.models.global.net.NetworkStack; /** * The simulation container. This is the main entry point for Minha. * It provides a method to create simulated hosts and interact with * the simulated world as whole. */ public class Simulation implements World { private static Logger logger = LoggerFactory.getLogger("pt.minha.API"); private ClassConfig cc; private Calibration nc; private Scheduler sched; private Network network; private List<Host> hosts = new ArrayList<Host>(); private List<Invocation> queue = new ArrayList<Invocation>(); private boolean stopping, closed; private Thread exitThread; private boolean running, blocked; private ReentrantLock lock = new ReentrantLock(); private Condition cond = lock.newCondition(); /** * Create a new simulation container. * @throws Exception */ public Simulation() throws Exception { // load calibration values Properties props = new Properties(); props.load(ClassLoader.getSystemClassLoader().getResourceAsStream("default.calibration.properties")); props = new Properties(props); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("calibration.properties"); if (is!=null) props.load(is); nc = new Calibration(props); // load instrumentation properties props = new Properties(); props.load(ClassLoader.getSystemClassLoader().getResourceAsStream("default.instrument.properties")); props = new Properties(props); is = ClassLoader.getSystemClassLoader().getResourceAsStream("instrument.properties"); if (is!=null) props.load(is); cc = new ClassConfig(props); // load simulation properties props = new Properties(); props.load(ClassLoader.getSystemClassLoader().getResourceAsStream("default.simulation.properties")); props = new Properties(props); is = ClassLoader.getSystemClassLoader().getResourceAsStream("simulation.properties"); if (is!=null) props.load(is); int procs = Integer.parseInt(props.getProperty("processors")); if (procs < 0) procs = Runtime.getRuntime().availableProcessors()*-procs; int timelines = Integer.parseInt(props.getProperty("timelines")); if (timelines < 0) timelines = procs*-timelines; long fuzzyness = Long.parseLong(props.getProperty("fuzzyness")); if (fuzzyness < 0) fuzzyness = (nc.getLineDelay(10)+nc.getLineLatency(10))*-fuzzyness; sched = new Scheduler(procs, timelines, fuzzyness); logger.info("using up to {} processors, {}ns fuzzyness", procs, fuzzyness); network = new Network(nc); } /* (non-Javadoc) * @see pt.minha.api.WorldI#createHost(java.lang.String) */ @Override public Host createHost(String ip) throws ContainerException { try { Timeline timeline = sched.createTimeline(); NetworkStack ns = new NetworkStack(timeline, ip, network); Resource cpu = new Resource(timeline, ns.getLocalAddress().getHostAddress()); Storage storage = new Storage(timeline, nc, ns.getLocalAddress().getHostAddress()); HostImpl host = new HostImpl(this, cc, timeline, cpu, ns, storage); hosts.add(host); return host; } catch (UnknownHostException e) { throw new ContainerException(e); } } /* (non-Javadoc) * @see pt.minha.api.WorldI#createHost() */ @Override public Host createHost() throws ContainerException { return createHost(null); } /* (non-Javadoc) * @see pt.minha.api.WorldI#getHosts() */ @Override public Collection<Host> getHosts() { return Collections.unmodifiableCollection(hosts); } /* (non-Javadoc) * @see pt.minha.api.WorldI#createEntries(int) */ @Override public Entry<Main>[] createEntries(int n) throws IllegalArgumentException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ContainerException { @SuppressWarnings("unchecked") Entry<Main>[] entries = new Entry[n]; for(int i = 0; i<n; i++) entries[i] = createHost().createProcess().createEntry(); return entries; } /* (non-Javadoc) * @see pt.minha.api.WorldI#createEntries(int, java.lang.Class, java.lang.String) */ @Override public <T> Entry<T>[] createEntries(int n, Class<T> intf, String impl) throws IllegalArgumentException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ContainerException { @SuppressWarnings("unchecked") Entry<T>[] entries = new Entry[n]; for(int i = 0; i<n; i++) entries[i] = createHost().createProcess().createEntry(intf, impl); return entries; } /* (non-Javadoc) * @see pt.minha.api.WorldI#createExits(pt.minha.api.Entry, java.lang.Class, T1) */ @Override public <T1,T2> Exit<T1>[] createExits(Entry<T2>[] entries, Class<T1> intf, T1 impl) { @SuppressWarnings("unchecked") Exit<T1>[] exits = new Exit[entries.length]; for(int i = 0; i<entries.length; i++) exits[i] = entries[i].getProcess().createExit(intf, impl); return exits; } /* (non-Javadoc) * @see pt.minha.api.WorldI#run() */ @Override public long run() { return runNanos(0); } /* (non-Javadoc) * @see pt.minha.api.WorldI#run(long, java.util.concurrent.TimeUnit) */ @Override public long run(long timeout, TimeUnit unit) { return runNanos(unit.toNanos(timeout)); } /* (non-Javadoc) * @see pt.minha.api.WorldI#runNanos(long) */ @Override public long runNanos(long nanosTimeout) { acquire(true); runSimulation(nanosTimeout); long time = sched.getTime(); release(true); return time; } /* (non-Javadoc) * @see pt.minha.api.WorldI#runAll(pt.minha.api.Milestone) */ @Override public long runAll(Milestone... milestones) throws IllegalArgumentException { try { acquire(true); for (Milestone m : milestones) { MilestoneImpl mi = (MilestoneImpl) m; if (!mi.isPending()) throw new IllegalArgumentException(); mi.setWaited(); } for (Milestone m : milestones) { while (!m.isComplete() && runSimulation(0)) ; } long time = sched.getTime(); return time; } finally { release(true); } } /** * Get the current calibration parameters. * @return calibration parameters */ public Calibration getCalibration() { return nc; } boolean runSimulation(long limit) { if (limit != 0) limit += sched.getTime(); boolean hasNext = sched.run(limit); lock.lock(); stopping = true; cond.signalAll(); while(exitThread != null) cond.awaitUninterruptibly(); lock.unlock(); return hasNext; } void acquire(boolean runner) { lock.lock(); if (running && (!blocked || runner)) throw new ConcurrentModificationException("reentering simulation"); if (runner) { running = true; lock.unlock(); } } void setBlocked(boolean blocked) { this.blocked = blocked; } void release(boolean runner) { if (runner) { lock.lock(); running = false; } lock.unlock(); } private static class Invocation { public Object target; public Method method; public Object[] args; private ResultHolder result; public Invocation(Object target, Method method, Object[] args, ResultHolder result) { this.target = target; this.method = method; this.args = args; this.result = result; } }; void handleInvoke(Object target, Method method, Object[] args, ResultHolder result) { lock.lock(); if (exitThread == null) { stopping = false; exitThread = new Thread() { public void run() { exitThread(); } }; exitThread.start(); } queue.add(new Invocation(target, method, args, result)); cond.signalAll(); lock.unlock(); } private void exitThread() { lock.lock(); try { while(true) { Invocation i = null; while(!stopping && queue.isEmpty()) cond.awaitUninterruptibly(); if (queue.isEmpty()) return; i = queue.remove(0); setBlocked(true); lock.unlock(); try { i.result.reportReturn(i.method.invoke(i.target, i.args)); } catch(InvocationTargetException ite) { i.result.reportException(ite.getTargetException()); } lock.lock(); setBlocked(false); } } catch (Exception e) { logger.error("unexpected exception on exit", e); } finally { exitThread = null; cond.signalAll(); lock.unlock(); } } /* (non-Javadoc) * @see pt.minha.api.WorldI#close() */ @Override public void close() throws IOException { if (closed) return; closed = true; ArrayList<Host> l = new ArrayList<Host>(hosts); for(Host h: l) h.close(); } void removeHost(Host host) { hosts.remove(host); } }