// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you 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 org.apache.cloudstack.hypervisor.xenserver; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeoutException; import org.apache.log4j.Logger; import org.apache.xmlrpc.XmlRpcException; import com.cloud.agent.api.StartupCommand; import com.cloud.hypervisor.xenserver.resource.XenServer620SP1Resource; import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; import com.xensource.xenapi.Connection; import com.xensource.xenapi.Event; import com.xensource.xenapi.EventBatch; import com.xensource.xenapi.Host; import com.xensource.xenapi.Pool; import com.xensource.xenapi.Task; import com.xensource.xenapi.Types; import com.xensource.xenapi.Types.XenAPIException; import com.xensource.xenapi.VM; /** * * XenServerResourceNewBase is an abstract base class that encapsulates how * CloudStack should interact with XenServer after a special XenServer * 6.2 hotfix. From here on, every Resource for future versions of * XenServer should use this as the base class. This base class lessens * the amount of load CloudStack places on Xapi because it doesn't use * polling as a means to collect data and figure out task completion. * * This base class differs from CitrixResourceBase in the following ways: * - VM states are detected using Event.from instead of polling. This * increases the number of threads CloudStack uses but the threads * are mostly idle just waiting for events from XenServer. * - stats are collected through the http interface rather than Xapi plugin. * This change may be promoted to CitrixResourceBase as it's also possible * in previous versions of XenServer. * - Asynchronous task completion is done throught Event.from rather than * polling. * */ public class XenServerResourceNewBase extends XenServer620SP1Resource { private static final Logger s_logger = Logger.getLogger(XenServerResourceNewBase.class); protected VmEventListener _listener = null; @Override public StartupCommand[] initialize() throws IllegalArgumentException { final StartupCommand[] cmds = super.initialize(); final Connection conn = getConnection(); Pool pool; try { pool = Pool.getByUuid(conn, _host.getPool()); final Pool.Record poolr = pool.getRecord(conn); final Host.Record masterRecord = poolr.master.getRecord(conn); if (_host.getUuid().equals(masterRecord.uuid)) { _listener = new VmEventListener(true); // // TODO disable event listener for now. Wait until everything else is ready // // _listener.start(); } else { _listener = new VmEventListener(false); } } catch (final XenAPIException e) { throw new CloudRuntimeException("Unable to determine who is the master", e); } catch (final XmlRpcException e) { throw new CloudRuntimeException("Unable to determine who is the master", e); } return cmds; } protected void waitForTask2(final Connection c, final Task task, final long pollInterval, final long timeout) throws XenAPIException, XmlRpcException, TimeoutException { final long beginTime = System.currentTimeMillis(); if (s_logger.isTraceEnabled()) { s_logger.trace("Task " + task.getNameLabel(c) + " (" + task.getType(c) + ") sent to " + c.getSessionReference() + " is pending completion with a " + timeout + "ms timeout"); } final Set<String> classes = new HashSet<String>(); classes.add("Task/" + task.toWireString()); String token = ""; final Double t = new Double(timeout / 1000); while (true) { final EventBatch map = Event.from(c, classes, token, t); token = map.token; @SuppressWarnings("unchecked") final Set<Event.Record> events = map.events; if (events.size() == 0) { final String msg = "No event for task " + task.toWireString(); s_logger.warn(msg); task.cancel(c); throw new TimeoutException(msg); } for (final Event.Record rec : events) { if (!(rec.snapshot instanceof Task.Record)) { if (s_logger.isDebugEnabled()) { s_logger.debug("Skipping over " + rec); } continue; } final Task.Record taskRecord = (Task.Record)rec.snapshot; if (taskRecord.status != Types.TaskStatusType.PENDING) { if (s_logger.isDebugEnabled()) { s_logger.debug("Task, ref:" + task.toWireString() + ", UUID:" + taskRecord.uuid + " is done " + taskRecord.status); } return; } else { if (s_logger.isDebugEnabled()) { s_logger.debug("Task: ref:" + task.toWireString() + ", UUID:" + taskRecord.uuid + " progress: " + taskRecord.progress); } } } if (System.currentTimeMillis() - beginTime > timeout) { final String msg = "Async " + timeout / 1000 + " seconds timeout for task " + task.toString(); s_logger.warn(msg); task.cancel(c); throw new TimeoutException(msg); } } } protected class VmEventListener extends Thread { boolean _stop = false; HashMap<String, Pair<String, VirtualMachine.State>> _changes = new HashMap<String, Pair<String, VirtualMachine.State>>(); boolean _isMaster; Set<String> _classes; String _token = ""; public VmEventListener(final boolean isMaster) { _isMaster = isMaster; _classes = new HashSet<String>(); _classes.add("VM"); } @Override public void run() { setName("XS-Listener-" + _host.getIp()); while (!_stop) { try { final Connection conn = getConnection(); EventBatch results; try { results = Event.from(conn, _classes, _token, new Double(30)); } catch (final Exception e) { s_logger.error("Retrying the waiting on VM events due to: ", e); continue; } _token = results.token; @SuppressWarnings("unchecked") final Set<Event.Record> events = results.events; for (final Event.Record event : events) { try { if (!(event.snapshot instanceof VM.Record)) { if (s_logger.isDebugEnabled()) { s_logger.debug("The snapshot is not a VM: " + event); } continue; } final VM.Record vm = (VM.Record)event.snapshot; String hostUuid = null; if (vm.residentOn != null && !vm.residentOn.toWireString().contains("OpaqueRef:NULL")) { hostUuid = vm.residentOn.getUuid(conn); } recordChanges(conn, vm, hostUuid); } catch (final Exception e) { s_logger.error("Skipping over " + event, e); } } } catch (final Throwable th) { s_logger.error("Exception caught in eventlistener thread: ", th); } } } protected void recordChanges(final Connection conn, final VM.Record rec, final String hostUuid) { } @Override public void start() { if (_isMaster) { // Throw away the initial set of events because they're history final Connection conn = getConnection(); EventBatch results; try { results = Event.from(conn, _classes, _token, new Double(30)); } catch (final Exception e) { s_logger.error("Retrying the waiting on VM events due to: ", e); throw new CloudRuntimeException("Unable to start a listener thread to listen to VM events", e); } _token = results.token; s_logger.debug("Starting the event listener thread for " + _host.getUuid()); super.start(); } } public boolean isListening() { return _isMaster; } public HashMap<String, Pair<String, VirtualMachine.State>> getChanges() { synchronized (_cluster.intern()) { if (_changes.size() == 0) { return null; } final HashMap<String, Pair<String, VirtualMachine.State>> diff = _changes; _changes = new HashMap<String, Pair<String, VirtualMachine.State>>(); return diff; } } public void signalStop() { _stop = true; interrupt(); } } }