/* * Copyright 2013 Philipp Leitner Licensed under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law * or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package at.ac.tuwien.infosys.jcloudscale.vm; import java.io.Closeable; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import javax.jms.JMSException; import javax.naming.NamingException; import at.ac.tuwien.infosys.jcloudscale.IJCloudScaleServer; import at.ac.tuwien.infosys.jcloudscale.configuration.JCloudScaleConfiguration; import at.ac.tuwien.infosys.jcloudscale.exception.JCloudScaleException; import at.ac.tuwien.infosys.jcloudscale.logging.Logged; import at.ac.tuwien.infosys.jcloudscale.messaging.IMQWrapper; import at.ac.tuwien.infosys.jcloudscale.messaging.TimeoutException; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.CreateObject; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.CreateReturnObject; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.DeleteObject; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.GetFieldValueObject; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.GetFieldValueResponseObject; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.InvocationResultReturnObject; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.KeepaliveObject; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.ReturnObject; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.SetFieldValueObject; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.ShutdownObject; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.StartInvokationObject; import at.ac.tuwien.infosys.jcloudscale.messaging.objects.StartInvokationReturnObject; import at.ac.tuwien.infosys.jcloudscale.server.ServerConfiguration; import at.ac.tuwien.infosys.jcloudscale.vm.localVm.ClientReturnListener; @Logged public class VirtualHostProxy implements IJCloudScaleServer, Closeable { protected IMQWrapper mq; protected Logger log; protected UUID serverId; protected long invocationTimeout; private ClientReturnListener returnListener; /** * Unlocked latch that is used for thread-safe unlock operation. */ private static final CountDownLatch unlockedLatch = new CountDownLatch(0); private ConcurrentHashMap<UUID, CountDownLatch> locks = new ConcurrentHashMap<>(); private ConcurrentHashMap<UUID, ReturnObject> returnValues = new ConcurrentHashMap<>(); public VirtualHostProxy(UUID id) { this.serverId = id; this.log = JCloudScaleConfiguration.getLogger(this); initQueue(); registerResponseListener(); } private void initQueue() { try { mq = JCloudScaleConfiguration.createMQWrapper(); ServerConfiguration config = JCloudScaleConfiguration .getConfiguration().server(); invocationTimeout = config.getInvocationTimeout(); mq.createTopicProducer(config.getRequestQueueName()); mq.createTopicConsumer(config.getResponseQueueName()); } catch (Exception e) { log.severe("Unable to init message queue in JCloudScale client: " + e.getMessage()); // e.printStackTrace(); throw new JCloudScaleException(e, "Unable to init message queue in JCloudScale client"); } } private void registerResponseListener() throws JCloudScaleException { returnListener = new ClientReturnListener(this); try { mq.registerListener(returnListener); } catch (JMSException e) { log.severe("Unable to register message listener in JCloudScale client: " + e.getMessage()); // e.printStackTrace(); throw new JCloudScaleException(e); } } @Override public void close() { if (this.returnListener != null) { returnListener.close(); returnListener = null; } try { if (mq != null) { mq.close(); mq = null; } locks.clear(); returnValues.clear(); } catch (Exception e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } } @Override public String createNewCloudObject(String classname, byte[] params, String[] paramNames) { UUID corrId = UUID.randomUUID(); CreateObject create = new CreateObject(); create.setClassname(classname); create.setParams(params); create.setParamNames(paramNames); try { lockForResponse(corrId); mq.onewayToCSHost(create, corrId, serverId); } catch (JMSException | InterruptedException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } CreateReturnObject ret; try { ret = (CreateReturnObject) waitForResponse(corrId); } catch (InterruptedException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } return ret.getReturnval(); } @Override public String startInvokingCloudObject(String objectId, String method, byte[] params, String[] paramNames) throws JCloudScaleException { StartInvokationObject start = new StartInvokationObject(); start.setId(objectId); start.setMethod(method); start.setParams(params); start.setParamNames(paramNames); UUID corrId = UUID.randomUUID(); try { lockForResponse(corrId); mq.onewayToCSHost(start, corrId, serverId); } catch (JMSException | InterruptedException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } StartInvokationReturnObject ret; try { ret = (StartInvokationReturnObject) waitForResponse(corrId); } catch (InterruptedException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } String retval = ret.getReturnval(); try { lockForResponse(UUID.fromString(retval)); } catch (InterruptedException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } return retval; } @Override public byte[] getCloudObjectField(String objectId, String field) { GetFieldValueObject request = new GetFieldValueObject(); request.setField(field); request.setObject(objectId); UUID corrId = UUID.randomUUID(); GetFieldValueResponseObject ret = null; try (IMQWrapper mq = JCloudScaleConfiguration.createMQWrapper()) { //TODO: creation of MQWrapper on every request introduces significant overhead. mq.createTopicProducer(JCloudScaleConfiguration.getConfiguration() .server().getRequestQueueName()); mq.createTopicConsumer(JCloudScaleConfiguration.getConfiguration() .server().getResponseQueueName(), "JMSCorrelationID = '" + corrId.toString() + "'"); ret = (GetFieldValueResponseObject) mq.requestResponseToCSHost( request, corrId, serverId); } catch (NamingException | JMSException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } catch (TimeoutException e) { log.severe("Timed out waiting for response from server to getfieldvalue request: " + e.getMessage()); throw new JCloudScaleException(e); } return ret.getFieldValue(); } @Override public void setCloudObjectField(String objectId, String field, byte[] value) { SetFieldValueObject request = new SetFieldValueObject(); request.setField(field); request.setObject(objectId); request.setValue(value); UUID corrId = UUID.randomUUID(); try (IMQWrapper mq = JCloudScaleConfiguration.createMQWrapper()) { //TODO: creation of MQWrapper on every request introduces significant overhead. mq.createTopicProducer(JCloudScaleConfiguration.getConfiguration() .server().getRequestQueueName()); mq.createTopicConsumer(JCloudScaleConfiguration.getConfiguration() .server().getResponseQueueName(), "JMSCorrelationID = '" + corrId.toString() + "'"); mq.requestResponseToCSHost(request, corrId, serverId); } catch (NamingException | JMSException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } catch (TimeoutException e) { log.severe("Timed out waiting for response from server to set-field field request: " + e.getMessage()); throw new JCloudScaleException(e); } } @Override public void suspendInvocation(String objectId, String request) throws JCloudScaleException { throw new JCloudScaleException("Not yet ported"); } @Override public void resumeInvocation(String objectId, String request) throws JCloudScaleException { throw new JCloudScaleException("Not yet ported"); } @Override public void destroyCloudObject(String id) throws JCloudScaleException { UUID corrId = UUID.randomUUID(); DeleteObject delete = new DeleteObject(); delete.setId(id); try { lockForResponse(corrId); mq.onewayToCSHost(delete, corrId, serverId); } catch (JMSException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } catch (InterruptedException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } try { waitForResponse(corrId); } catch (InterruptedException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } } @Override public void keepAliveCloudObject(UUID id) throws JCloudScaleException { KeepaliveObject keep = new KeepaliveObject(); keep.setId(id); UUID corrId = UUID.randomUUID(); try { mq.onewayToCSHost(keep, corrId, serverId); } catch (JMSException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } } @Override public String getCloudObjectType(String id) throws JCloudScaleException { throw new JCloudScaleException("Not yet ported"); } @Override public void shutdown() { ShutdownObject shutdown = new ShutdownObject(); try { mq.onewayToCSHost(shutdown, null, serverId); } catch (JMSException e) { log.severe(e.getMessage()); throw new JCloudScaleException(e); } close(); } public void unlock(UUID corrid) { // // If there was latch in the collection, we have to unlock it. // if not, we put there unlocked already latch. // CountDownLatch previousLatch = locks.putIfAbsent(corrid, unlockedLatch); if(previousLatch != null) previousLatch.countDown(); } public void setResult(UUID corrId, ReturnObject ret) { returnValues.put(corrId, ret); } public byte[] waitForResult(String invocationId) throws JCloudScaleException, InterruptedException { ReturnObject ret = waitForResponse(UUID.fromString(invocationId)); InvocationResultReturnObject invResult = (InvocationResultReturnObject) ret; return invResult.getResult(); } protected void lockForResponse(UUID corrId) throws InterruptedException { // we have to try putting new lock to handle properly the case when the reply arrives faster than we lock. if(locks.putIfAbsent(corrId, new CountDownLatch(1)) != null) log.fine("Attempted to lock for request "+corrId+", but request with this id was already locked (possibly because the reply is already here)."); } protected ReturnObject waitForResponse(UUID corrId) throws InterruptedException, JCloudScaleException { // when we get response, the latch will be unlocked. CountDownLatch latch = locks.get(corrId); if(latch != null) // if the latch is null, we are released already (or not locked yet?). { if(!latch.await(invocationTimeout, TimeUnit.MILLISECONDS)) throw new JCloudScaleException("Failed to receive reply to the invocation "+corrId+" within timeout "+invocationTimeout+" ms."); else if(!locks.remove(corrId, latch)) throw new JCloudScaleException("Failed to remove locking object responsible for invocation "+corrId+": lock object was replaced during response waiting."); } if (returnValues.containsKey(corrId)) { ReturnObject ret = returnValues.get(corrId); returnValues.remove(corrId); checkForException(ret); return ret; } else { throw new JCloudScaleException( "Internal Exception: got notified of incoming result, but no result is available"); } } private void checkForException(ReturnObject ret) throws JCloudScaleException { if (ret.hasFailed()) { // If a JCloudScaleException was received, rethrow it immediately // instead of wrapping it Throwable ex = ret.getException(); if (ex instanceof JCloudScaleException) throw (JCloudScaleException) ex; else throw new JCloudScaleException(ret.getException()); } } }