| // 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 com.cloud.agent; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| |
| import javax.naming.ConfigurationException; |
| |
| import org.apache.log4j.Logger; |
| |
| import com.cloud.agent.Agent.ExitStatus; |
| import com.cloud.agent.api.Command; |
| import com.cloud.agent.api.StartupVMMAgentCommand; |
| import com.cloud.agent.dao.StorageComponent; |
| import com.cloud.agent.dao.impl.PropertiesStorage; |
| import com.cloud.agent.transport.Request; |
| import com.cloud.resource.ServerResource; |
| import com.cloud.utils.NumbersUtil; |
| import com.cloud.utils.PropertiesUtil; |
| import com.cloud.utils.backoff.BackoffAlgorithm; |
| import com.cloud.utils.backoff.impl.ConstantTimeBackoff; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| import com.cloud.utils.net.MacAddress; |
| import com.cloud.utils.nio.HandlerFactory; |
| import com.cloud.utils.nio.Link; |
| import com.cloud.utils.nio.NioServer; |
| import com.cloud.utils.nio.Task; |
| |
| /** |
| * Implementation of agent shell to run the agents on System Center Virtual Machine manager |
| **/ |
| |
| public class VmmAgentShell implements IAgentShell, HandlerFactory { |
| |
| private static final Logger s_logger = Logger.getLogger(VmmAgentShell.class.getName()); |
| private final Properties _properties = new Properties(); |
| private final Map<String, Object> _cmdLineProperties = new HashMap<String, Object>(); |
| private StorageComponent _storage; |
| private BackoffAlgorithm _backoff; |
| private String _version; |
| private String _zone; |
| private String _pod; |
| private String _cluster; |
| private String _host; |
| private String _privateIp; |
| private int _port; |
| private int _proxyPort; |
| private int _workers; |
| private String _guid; |
| static private NioServer _connection; |
| static private int _listenerPort=9000; |
| private int _nextAgentId = 1; |
| private volatile boolean _exit = false; |
| private int _pingRetries; |
| private final Thread _consoleProxyMain = null; |
| private final List<Agent> _agents = new ArrayList<Agent>(); |
| |
| public VmmAgentShell() { |
| } |
| |
| @Override |
| public Properties getProperties() { |
| return _properties; |
| } |
| |
| @Override |
| public BackoffAlgorithm getBackoffAlgorithm() { |
| return _backoff; |
| } |
| |
| @Override |
| public int getPingRetries() { |
| return _pingRetries; |
| } |
| |
| @Override |
| public String getZone() { |
| return _zone; |
| } |
| |
| @Override |
| public String getPod() { |
| return _pod; |
| } |
| |
| @Override |
| public String getHost() { |
| return _host; |
| } |
| |
| @Override |
| public String getPrivateIp() { |
| return _privateIp; |
| } |
| |
| @Override |
| public int getPort() { |
| return _port; |
| } |
| |
| @Override |
| public int getProxyPort() { |
| return _proxyPort; |
| } |
| |
| @Override |
| public int getWorkers() { |
| return _workers; |
| } |
| |
| @Override |
| public String getGuid() { |
| return _guid; |
| } |
| |
| @Override |
| public void upgradeAgent(String url) { |
| // TODO Auto-generated method stub |
| |
| } |
| |
| @Override |
| public String getVersion() { |
| return _version; |
| } |
| |
| @Override |
| public Map<String, Object> getCmdLineProperties() { |
| // TODO Auto-generated method stub |
| return _cmdLineProperties; |
| } |
| |
| public String getProperty(String prefix, String name) { |
| if(prefix != null) |
| return _properties.getProperty(prefix + "." + name); |
| |
| return _properties.getProperty(name); |
| } |
| |
| @Override |
| public String getPersistentProperty(String prefix, String name) { |
| if(prefix != null) |
| return _storage.get(prefix + "." + name); |
| return _storage.get(name); |
| } |
| |
| @Override |
| public void setPersistentProperty(String prefix, String name, String value) { |
| if(prefix != null) |
| _storage.persist(prefix + "." + name, value); |
| else |
| _storage.persist(name, value); |
| } |
| |
| private void loadProperties() throws ConfigurationException { |
| final File file = PropertiesUtil.findConfigFile("agent.properties"); |
| if (file == null) { |
| throw new ConfigurationException("Unable to find agent.properties."); |
| } |
| |
| s_logger.info("agent.properties found at " + file.getAbsolutePath()); |
| |
| try { |
| _properties.load(new FileInputStream(file)); |
| } catch (final FileNotFoundException ex) { |
| throw new CloudRuntimeException("Cannot find the file: " + file.getAbsolutePath(), ex); |
| } catch (final IOException ex) { |
| throw new CloudRuntimeException("IOException in reading " + file.getAbsolutePath(), ex); |
| } |
| } |
| |
| protected boolean parseCommand(final String[] args) throws ConfigurationException { |
| String host = null; |
| String workers = null; |
| String port = null; |
| String zone = null; |
| String pod = null; |
| String guid = null; |
| for (int i = 0; i < args.length; i++) { |
| final String[] tokens = args[i].split("="); |
| if (tokens.length != 2) { |
| System.out.println("Invalid Parameter: " + args[i]); |
| continue; |
| } |
| |
| // save command line properties |
| _cmdLineProperties.put(tokens[0], tokens[1]); |
| |
| if (tokens[0].equalsIgnoreCase("port")) { |
| port = tokens[1]; |
| } else if (tokens[0].equalsIgnoreCase("threads")) { |
| workers = tokens[1]; |
| } else if (tokens[0].equalsIgnoreCase("host")) { |
| host = tokens[1]; |
| } else if(tokens[0].equalsIgnoreCase("zone")) { |
| zone = tokens[1]; |
| } else if(tokens[0].equalsIgnoreCase("pod")) { |
| pod = tokens[1]; |
| } else if(tokens[0].equalsIgnoreCase("guid")) { |
| guid = tokens[1]; |
| } else if(tokens[0].equalsIgnoreCase("eth1ip")) { |
| _privateIp = tokens[1]; |
| } |
| } |
| |
| if (port == null) { |
| port = getProperty(null, "port"); |
| } |
| |
| _port = NumbersUtil.parseInt(port, 8250); |
| |
| _proxyPort = NumbersUtil.parseInt(getProperty(null, "consoleproxy.httpListenPort"), 443); |
| |
| if (workers == null) { |
| workers = getProperty(null, "workers"); |
| } |
| |
| _workers = NumbersUtil.parseInt(workers, 5); |
| |
| if (host == null) { |
| host = getProperty(null, "host"); |
| } |
| |
| if (host == null) { |
| host = "localhost"; |
| } |
| _host = host; |
| |
| if(zone != null) |
| _zone = zone; |
| else |
| _zone = getProperty(null, "zone"); |
| if (_zone == null || (_zone.startsWith("@") && _zone.endsWith("@"))) { |
| _zone = "default"; |
| } |
| |
| if(pod != null) |
| _pod = pod; |
| else |
| _pod = getProperty(null, "pod"); |
| if (_pod == null || (_pod.startsWith("@") && _pod.endsWith("@"))) { |
| _pod = "default"; |
| } |
| |
| if (_host == null || (_host.startsWith("@") && _host.endsWith("@"))) { |
| throw new ConfigurationException("Host is not configured correctly: " + _host); |
| } |
| |
| final String retries = getProperty(null, "ping.retries"); |
| _pingRetries = NumbersUtil.parseInt(retries, 5); |
| |
| String value = getProperty(null, "developer"); |
| boolean developer = Boolean.parseBoolean(value); |
| |
| if(guid != null) |
| _guid = guid; |
| else |
| _guid = getProperty(null, "guid"); |
| if (_guid == null) { |
| if (!developer) { |
| throw new ConfigurationException("Unable to find the guid"); |
| } |
| _guid = MacAddress.getMacAddress().toString(":"); |
| } |
| |
| return true; |
| } |
| |
| private void launchAgentFromTypeInfo() throws ConfigurationException { |
| String typeInfo = getProperty(null, "type"); |
| if (typeInfo == null) { |
| s_logger.error("Unable to retrieve the type"); |
| throw new ConfigurationException("Unable to retrieve the type of this agent."); |
| } |
| s_logger.trace("Launching agent based on type=" + typeInfo); |
| } |
| |
| private void launchAgent() throws ConfigurationException { |
| String resourceClassNames = getProperty(null, "resource"); |
| s_logger.trace("resource=" + resourceClassNames); |
| if(resourceClassNames != null) { |
| launchAgentFromClassInfo(resourceClassNames); |
| return; |
| } |
| |
| launchAgentFromTypeInfo(); |
| } |
| |
| private void init(String[] args) throws ConfigurationException{ |
| |
| final Class<?> c = this.getClass(); |
| _version = c.getPackage().getImplementationVersion(); |
| if (_version == null) { |
| throw new CloudRuntimeException("Unable to find the implementation version of this agent"); |
| } |
| s_logger.info("Implementation Version is " + _version); |
| |
| parseCommand(args); |
| |
| s_logger.info("Defaulting to using properties file for storage"); |
| _storage = new PropertiesStorage(); |
| _storage.configure("Storage", new HashMap<String, Object>()); |
| |
| // merge with properties from command line to let resource access command line parameters |
| for(Map.Entry<String, Object> cmdLineProp : getCmdLineProperties().entrySet()) { |
| _properties.put(cmdLineProp.getKey(), cmdLineProp.getValue()); |
| } |
| |
| s_logger.info("Defaulting to the constant time backoff algorithm"); |
| _backoff = new ConstantTimeBackoff(); |
| _backoff.configure("ConstantTimeBackoff", new HashMap<String, Object>()); |
| } |
| |
| private void launchAgentFromClassInfo(String resourceClassNames) throws ConfigurationException { |
| String[] names = resourceClassNames.split("\\|"); |
| for(String name: names) { |
| Class<?> impl; |
| try { |
| impl = Class.forName(name); |
| final Constructor<?> constructor = impl.getDeclaredConstructor(); |
| constructor.setAccessible(true); |
| ServerResource resource = (ServerResource)constructor.newInstance(); |
| launchAgent(getNextAgentId(), resource); |
| } catch (final ClassNotFoundException e) { |
| throw new ConfigurationException("Resource class not found: " + name); |
| } catch (final SecurityException e) { |
| throw new ConfigurationException("Security excetion when loading resource: " + name); |
| } catch (final NoSuchMethodException e) { |
| throw new ConfigurationException("Method not found excetion when loading resource: " + name); |
| } catch (final IllegalArgumentException e) { |
| throw new ConfigurationException("Illegal argument excetion when loading resource: " + name); |
| } catch (final InstantiationException e) { |
| throw new ConfigurationException("Instantiation excetion when loading resource: " + name); |
| } catch (final IllegalAccessException e) { |
| throw new ConfigurationException("Illegal access exception when loading resource: " + name); |
| } catch (final InvocationTargetException e) { |
| throw new ConfigurationException("Invocation target exception when loading resource: " + name); |
| } |
| } |
| } |
| |
| private void launchAgent(int localAgentId, ServerResource resource) throws ConfigurationException { |
| // we don't track agent after it is launched for now |
| Agent agent = new Agent(this, localAgentId, resource); |
| _agents.add(agent); |
| agent.start(); |
| } |
| |
| public synchronized int getNextAgentId() { |
| return _nextAgentId++; |
| } |
| |
| private void run(String[] args) { |
| |
| try { |
| System.setProperty("java.net.preferIPv4Stack","true"); |
| loadProperties(); |
| init(args); |
| |
| String instance = getProperty(null, "instance"); |
| if (instance == null) { |
| instance = ""; |
| } else { |
| instance += "."; |
| } |
| |
| // TODO need to do this check. For Agentshell running on windows needs different approach |
| //final String run = "agent." + instance + "pid"; |
| //s_logger.debug("Checking to see if " + run + "exists."); |
| //ProcessUtil.pidCheck(run); |
| |
| |
| // TODO: For Hyper-V agent.properties need to be revamped to support multiple agents |
| // corresponding to multiple clusters but running on a SCVMM host |
| |
| // read the persistent storage and launch the agents |
| //launchAgent(); |
| |
| // FIXME get rid of this approach of agent listening for boot strap commands from the management server |
| |
| // now listen for bootstrap request from the management server and launch agents |
| _connection = new NioServer("VmmAgentShell", _listenerPort, 1, this); |
| _connection.start(); |
| s_logger.info("SCVMM agent is listening on port " +_listenerPort + " for bootstrap command from management server"); |
| while(_connection.isRunning()); |
| } catch(final ConfigurationException e) { |
| s_logger.error("Unable to start agent: " + e.getMessage()); |
| System.out.println("Unable to start agent: " + e.getMessage()); |
| System.exit(ExitStatus.Configuration.value()); |
| } catch (final Exception e) { |
| s_logger.error("Unable to start agent: ", e); |
| System.out.println("Unable to start agent: " + e.getMessage()); |
| System.exit(ExitStatus.Error.value()); |
| } |
| } |
| |
| @Override |
| public Task create(com.cloud.utils.nio.Task.Type type, Link link, |
| byte[] data) { |
| return new AgentBootStrapHandler(type, link, data); |
| } |
| |
| public void stop() { |
| _exit = true; |
| if(_consoleProxyMain != null) { |
| _consoleProxyMain.interrupt(); |
| } |
| } |
| |
| public static void main(String[] args) { |
| |
| VmmAgentShell shell = new VmmAgentShell(); |
| Runtime.getRuntime().addShutdownHook(new ShutdownThread(shell)); |
| shell.run(args); |
| } |
| |
| // class to handle the bootstrap command from the management server |
| private class AgentBootStrapHandler extends Task { |
| |
| public AgentBootStrapHandler(Task.Type type, Link link, byte[] data) { |
| super(type, link, data); |
| } |
| |
| @Override |
| protected void doTask(Task task) throws Exception { |
| final Type type = task.getType(); |
| s_logger.info("recieved task of type "+ type.toString() +" to handle in BootStrapTakHandler"); |
| if (type == Task.Type.DATA) |
| { |
| final byte[] data = task.getData(); |
| final Request request = Request.parse(data); |
| final Command cmd = request.getCommand(); |
| |
| if (cmd instanceof StartupVMMAgentCommand) { |
| |
| StartupVMMAgentCommand vmmCmd = (StartupVMMAgentCommand) cmd; |
| |
| _zone = Long.toString(vmmCmd.getDataCenter()); |
| _cmdLineProperties.put("zone", _zone); |
| |
| _pod = Long.toString(vmmCmd.getPod()); |
| _cmdLineProperties.put("pod", _pod); |
| |
| _cluster = vmmCmd.getClusterName(); |
| _cmdLineProperties.put("cluster", _cluster); |
| |
| _guid = vmmCmd.getGuid(); |
| _cmdLineProperties.put("guid", _guid); |
| |
| _host = vmmCmd.getManagementServerIP(); |
| _port = NumbersUtil.parseInt(vmmCmd.getport(), 8250); |
| |
| s_logger.info("Recieved boot strap command from management server with parameters " + |
| " Zone:"+ _zone + " "+ |
| " Cluster:"+ _cluster + " "+ |
| " pod:"+_pod + " "+ |
| " host:"+ _host +" "+ |
| " port:"+_port); |
| |
| launchAgentFromClassInfo("com.cloud.hypervisor.hyperv.resource.HypervResource"); |
| |
| // TODO: persist the info in agent.properties for agent restarts |
| } |
| } |
| } |
| } |
| |
| private static class ShutdownThread extends Thread { |
| VmmAgentShell _shell; |
| public ShutdownThread(VmmAgentShell shell) { |
| this._shell = shell; |
| } |
| |
| @Override |
| public void run() { |
| _shell.stop(); |
| } |
| } |
| |
| } |