| // 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.hypervisor; | |
| import java.util.List; | |
| import java.util.Map; | |
| import javax.ejb.Local; | |
| import javax.inject.Inject; | |
| import javax.naming.ConfigurationException; | |
| import org.apache.log4j.Logger; | |
| import org.springframework.stereotype.Component; | |
| import org.apache.cloudstack.api.ResourceDetail; | |
| import org.apache.cloudstack.framework.config.dao.ConfigurationDao; | |
| import com.cloud.agent.AgentManager; | |
| import com.cloud.agent.StartupCommandProcessor; | |
| import com.cloud.agent.api.StartupCommand; | |
| import com.cloud.agent.api.StartupRoutingCommand; | |
| import com.cloud.agent.api.StartupStorageCommand; | |
| import com.cloud.agent.manager.authn.AgentAuthnException; | |
| import com.cloud.configuration.ConfigurationManager; | |
| import com.cloud.configuration.ZoneConfig; | |
| import com.cloud.dc.ClusterVO; | |
| import com.cloud.dc.DataCenterVO; | |
| import com.cloud.dc.HostPodVO; | |
| import com.cloud.dc.dao.ClusterDao; | |
| import com.cloud.dc.dao.DataCenterDao; | |
| import com.cloud.dc.dao.DataCenterDetailsDao; | |
| import com.cloud.dc.dao.HostPodDao; | |
| import com.cloud.exception.ConnectionException; | |
| import com.cloud.host.Host; | |
| import com.cloud.host.HostVO; | |
| import com.cloud.host.dao.HostDao; | |
| import com.cloud.hypervisor.Hypervisor.HypervisorType; | |
| import com.cloud.utils.component.AdapterBase; | |
| import com.cloud.utils.exception.CloudRuntimeException; | |
| import com.cloud.utils.net.MacAddress; | |
| import com.cloud.utils.net.NetUtils; | |
| /** | |
| * Creates a host record and supporting records such as pod and ip address | |
| * | |
| */ | |
| @Component | |
| @Local(value = StartupCommandProcessor.class) | |
| public class CloudZonesStartupProcessor extends AdapterBase implements StartupCommandProcessor { | |
| private static final Logger s_logger = Logger.getLogger(CloudZonesStartupProcessor.class); | |
| @Inject | |
| ClusterDao _clusterDao = null; | |
| @Inject | |
| ConfigurationDao _configDao = null; | |
| @Inject | |
| DataCenterDao _zoneDao = null; | |
| @Inject | |
| HostDao _hostDao = null; | |
| @Inject | |
| HostPodDao _podDao = null; | |
| @Inject | |
| DataCenterDetailsDao _zoneDetailsDao = null; | |
| @Inject | |
| AgentManager _agentManager = null; | |
| @Inject | |
| ConfigurationManager _configurationManager = null; | |
| long _nodeId = -1; | |
| @Override | |
| public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { | |
| _agentManager.registerForInitialConnects(this, false); | |
| if (_nodeId == -1) { | |
| // FIXME: We really should not do this like this. It should be done | |
| // at config time and is stored as a config variable. | |
| _nodeId = MacAddress.getMacAddress().toLong(); | |
| } | |
| return true; | |
| } | |
| @Override | |
| public boolean processInitialConnect(StartupCommand[] cmd) throws ConnectionException { | |
| StartupCommand startup = cmd[0]; | |
| if (startup instanceof StartupRoutingCommand) { | |
| return processHostStartup((StartupRoutingCommand)startup); | |
| } else if (startup instanceof StartupStorageCommand) { | |
| return processStorageStartup((StartupStorageCommand)startup); | |
| } | |
| return false; | |
| } | |
| protected boolean processHostStartup(StartupRoutingCommand startup) throws ConnectionException { | |
| /* | |
| boolean found = false; | |
| Type type = Host.Type.Routing; | |
| final Map<String, String> hostDetails = startup.getHostDetails(); | |
| HostVO server = _hostDao.findByGuid(startup.getGuid()); | |
| if (server == null) { | |
| server = _hostDao.findByGuid(startup.getGuidWithoutResource()); | |
| } | |
| if (server != null && server.getRemoved() == null) { | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Found the host " + server.getId() + " by guid: " | |
| + startup.getGuid()); | |
| } | |
| found = true; | |
| } else { | |
| server = new HostVO(startup.getGuid()); | |
| } | |
| server.setDetails(hostDetails); | |
| try { | |
| updateComputeHost(server, startup, type); | |
| } catch (AgentAuthnException e) { | |
| throw new ConnectionException(true, "Failed to authorize host, invalid configuration", e); | |
| } | |
| if (!found) { | |
| server.setHostAllocationState(Host.HostAllocationState.Enabled); | |
| server = _hostDao.persist(server); | |
| } else { | |
| if (!_hostDao.connect(server, _nodeId)) { | |
| throw new CloudRuntimeException( | |
| "Agent cannot connect because the current state is " | |
| + server.getStatus().toString()); | |
| } | |
| s_logger.info("Old " + server.getType().toString() | |
| + " host reconnected w/ id =" + server.getId()); | |
| } | |
| */ | |
| return true; | |
| } | |
| protected void updateComputeHost(final HostVO host, final StartupCommand startup, final Host.Type type) throws AgentAuthnException { | |
| String zoneToken = startup.getDataCenter(); | |
| if (zoneToken == null) { | |
| s_logger.warn("No Zone Token passed in, cannot not find zone for the agent"); | |
| throw new AgentAuthnException("No Zone Token passed in, cannot not find zone for agent"); | |
| } | |
| DataCenterVO zone = _zoneDao.findByToken(zoneToken); | |
| if (zone == null) { | |
| zone = _zoneDao.findByName(zoneToken); | |
| if (zone == null) { | |
| try { | |
| long zoneId = Long.parseLong(zoneToken); | |
| zone = _zoneDao.findById(zoneId); | |
| if (zone == null) { | |
| throw new AgentAuthnException("Could not find zone for agent with token " + zoneToken); | |
| } | |
| } catch (NumberFormatException nfe) { | |
| throw new AgentAuthnException("Could not find zone for agent with token " + zoneToken); | |
| } | |
| } | |
| } | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Successfully loaded the DataCenter from the zone token passed in "); | |
| } | |
| long zoneId = zone.getId(); | |
| ResourceDetail maxHostsInZone = _zoneDetailsDao.findDetail(zoneId, ZoneConfig.MaxHosts.key()); | |
| if (maxHostsInZone != null) { | |
| long maxHosts = new Long(maxHostsInZone.getValue()).longValue(); | |
| long currentCountOfHosts = _hostDao.countRoutingHostsByDataCenter(zoneId); | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Number of hosts in Zone:" + currentCountOfHosts + ", max hosts limit: " + maxHosts); | |
| } | |
| if (currentCountOfHosts >= maxHosts) { | |
| throw new AgentAuthnException("Number of running Routing hosts in the Zone:" + zone.getName() + " is already at the max limit:" + maxHosts + | |
| ", cannot start one more host"); | |
| } | |
| } | |
| HostPodVO pod = null; | |
| if (startup.getPrivateIpAddress() == null) { | |
| s_logger.warn("No private IP address passed in for the agent, cannot not find pod for agent"); | |
| throw new AgentAuthnException("No private IP address passed in for the agent, cannot not find pod for agent"); | |
| } | |
| if (startup.getPrivateNetmask() == null) { | |
| s_logger.warn("No netmask passed in for the agent, cannot not find pod for agent"); | |
| throw new AgentAuthnException("No netmask passed in for the agent, cannot not find pod for agent"); | |
| } | |
| if (host.getPodId() != null) { | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Pod is already created for this agent, looks like agent is reconnecting..."); | |
| } | |
| pod = _podDao.findById(host.getPodId()); | |
| if (!checkCIDR(type, pod, startup.getPrivateIpAddress(), startup.getPrivateNetmask())) { | |
| pod = null; | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Subnet of Pod does not match the subnet of the agent, not using this Pod: " + host.getPodId()); | |
| } | |
| } else { | |
| updatePodNetmaskIfNeeded(pod, startup.getPrivateNetmask()); | |
| } | |
| } | |
| if (pod == null) { | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Trying to detect the Pod to use from the agent's ip address and netmask passed in "); | |
| } | |
| //deduce pod | |
| boolean podFound = false; | |
| List<HostPodVO> podsInZone = _podDao.listByDataCenterId(zoneId); | |
| for (HostPodVO hostPod : podsInZone) { | |
| if (checkCIDR(type, hostPod, startup.getPrivateIpAddress(), startup.getPrivateNetmask())) { | |
| pod = hostPod; | |
| //found the default POD having the same subnet. | |
| updatePodNetmaskIfNeeded(pod, startup.getPrivateNetmask()); | |
| podFound = true; | |
| break; | |
| } | |
| } | |
| if (!podFound) { | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Creating a new Pod since no default Pod found that matches the agent's ip address and netmask passed in "); | |
| } | |
| if (startup.getGatewayIpAddress() == null) { | |
| s_logger.warn("No Gateway IP address passed in for the agent, cannot create a new pod for the agent"); | |
| throw new AgentAuthnException("No Gateway IP address passed in for the agent, cannot create a new pod for the agent"); | |
| } | |
| //auto-create a new pod, since pod matching the agent's ip is not found | |
| String podName = "POD-" + (podsInZone.size() + 1); | |
| try { | |
| String gateway = startup.getGatewayIpAddress(); | |
| String cidr = NetUtils.getCidrFromGatewayAndNetmask(gateway, startup.getPrivateNetmask()); | |
| String[] cidrPair = cidr.split("\\/"); | |
| String cidrAddress = cidrPair[0]; | |
| long cidrSize = Long.parseLong(cidrPair[1]); | |
| String startIp = NetUtils.getIpRangeStartIpFromCidr(cidrAddress, cidrSize); | |
| String endIp = NetUtils.getIpRangeEndIpFromCidr(cidrAddress, cidrSize); | |
| pod = _configurationManager.createPod(-1, podName, zoneId, gateway, cidr, startIp, endIp, null, true); | |
| } catch (Exception e) { | |
| // no longer tolerate exception during the cluster creation phase | |
| throw new CloudRuntimeException("Unable to create new Pod " + podName + " in Zone: " + zoneId, e); | |
| } | |
| } | |
| } | |
| final StartupRoutingCommand scc = (StartupRoutingCommand)startup; | |
| ClusterVO cluster = null; | |
| if (host.getClusterId() != null) { | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Cluster is already created for this agent, looks like agent is reconnecting..."); | |
| } | |
| cluster = _clusterDao.findById(host.getClusterId()); | |
| } | |
| if (cluster == null) { | |
| //auto-create cluster - assume one host per cluster | |
| String clusterName = "Cluster-" + startup.getPrivateIpAddress(); | |
| ClusterVO existingCluster = _clusterDao.findBy(clusterName, pod.getId()); | |
| if (existingCluster != null) { | |
| cluster = existingCluster; | |
| } else { | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Creating a new Cluster for this agent with name: " + clusterName + " in Pod: " + pod.getId() + ", in Zone:" + zoneId); | |
| } | |
| cluster = new ClusterVO(zoneId, pod.getId(), clusterName); | |
| cluster.setHypervisorType(scc.getHypervisorType().toString()); | |
| try { | |
| cluster = _clusterDao.persist(cluster); | |
| } catch (Exception e) { | |
| // no longer tolerate exception during the cluster creation phase | |
| throw new CloudRuntimeException("Unable to create cluster " + clusterName + " in pod " + pod.getId() + " and data center " + zoneId, e); | |
| } | |
| } | |
| } | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Detected Zone: " + zoneId + ", Pod: " + pod.getId() + ", Cluster:" + cluster.getId()); | |
| } | |
| host.setDataCenterId(zone.getId()); | |
| host.setPodId(pod.getId()); | |
| host.setClusterId(cluster.getId()); | |
| host.setPrivateIpAddress(startup.getPrivateIpAddress()); | |
| host.setPrivateNetmask(startup.getPrivateNetmask()); | |
| host.setPrivateMacAddress(startup.getPrivateMacAddress()); | |
| host.setPublicIpAddress(startup.getPublicIpAddress()); | |
| host.setPublicMacAddress(startup.getPublicMacAddress()); | |
| host.setPublicNetmask(startup.getPublicNetmask()); | |
| host.setStorageIpAddress(startup.getStorageIpAddress()); | |
| host.setStorageMacAddress(startup.getStorageMacAddress()); | |
| host.setStorageNetmask(startup.getStorageNetmask()); | |
| host.setVersion(startup.getVersion()); | |
| host.setName(startup.getName()); | |
| host.setType(type); | |
| host.setStorageUrl(startup.getIqn()); | |
| host.setLastPinged(System.currentTimeMillis() >> 10); | |
| host.setCaps(scc.getCapabilities()); | |
| host.setCpus(scc.getCpus()); | |
| host.setTotalMemory(scc.getMemory()); | |
| host.setSpeed(scc.getSpeed()); | |
| HypervisorType hyType = scc.getHypervisorType(); | |
| host.setHypervisorType(hyType); | |
| host.setHypervisorVersion(scc.getHypervisorVersion()); | |
| } | |
| private boolean checkCIDR(Host.Type type, HostPodVO pod, String serverPrivateIP, String serverPrivateNetmask) { | |
| if (serverPrivateIP == null) { | |
| return true; | |
| } | |
| // Get the CIDR address and CIDR size | |
| String cidrAddress = pod.getCidrAddress(); | |
| long cidrSize = pod.getCidrSize(); | |
| // If the server's private IP address is not in the same subnet as the | |
| // pod's CIDR, return false | |
| String cidrSubnet = NetUtils.getCidrSubNet(cidrAddress, cidrSize); | |
| String serverSubnet = NetUtils.getSubNet(serverPrivateIP, serverPrivateNetmask); | |
| if (!cidrSubnet.equals(serverSubnet)) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| private void updatePodNetmaskIfNeeded(HostPodVO pod, String agentNetmask) { | |
| // If the server's private netmask is less inclusive than the pod's CIDR | |
| // netmask, update cidrSize of the default POD | |
| //(reason: we are maintaining pods only for internal accounting.) | |
| long cidrSize = pod.getCidrSize(); | |
| String cidrNetmask = NetUtils.getCidrSubNet("255.255.255.255", cidrSize); | |
| long cidrNetmaskNumeric = NetUtils.ip2Long(cidrNetmask); | |
| long serverNetmaskNumeric = NetUtils.ip2Long(agentNetmask);// | |
| if (serverNetmaskNumeric > cidrNetmaskNumeric) { | |
| //update pod's cidrsize | |
| int newCidrSize = new Long(NetUtils.getCidrSize(agentNetmask)).intValue(); | |
| pod.setCidrSize(newCidrSize); | |
| _podDao.update(pod.getId(), pod); | |
| } | |
| } | |
| protected boolean processStorageStartup(StartupStorageCommand startup) throws ConnectionException { | |
| /* | |
| if (startup.getResourceType() != Storage.StorageResourceType.LOCAL_SECONDARY_STORAGE) { | |
| return false; | |
| } | |
| boolean found = false; | |
| Type type = Host.Type.LocalSecondaryStorage; | |
| final Map<String, String> hostDetails = startup.getHostDetails(); | |
| HostVO server = _hostDao.findByGuid(startup.getGuid()); | |
| if (server == null) { | |
| server = _hostDao.findByGuid(startup.getGuidWithoutResource()); | |
| } | |
| if (server != null && server.getRemoved() == null) { | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Found the host " + server.getId() + " by guid: " | |
| + startup.getGuid()); | |
| } | |
| found = true; | |
| } else { | |
| server = new HostVO(startup.getGuid()); | |
| } | |
| server.setDetails(hostDetails); | |
| try { | |
| updateSecondaryHost(server, startup, type); | |
| } catch (AgentAuthnException e) { | |
| throw new ConnectionException(true, "Failed to authorize host, invalid configuration", e); | |
| } | |
| if (!found) { | |
| server.setHostAllocationState(Host.HostAllocationState.Enabled); | |
| server = _hostDao.persist(server); | |
| } else { | |
| if (!_hostDao.connect(server, _nodeId)) { | |
| throw new CloudRuntimeException( | |
| "Agent cannot connect because the current state is " | |
| + server.getStatus().toString()); | |
| } | |
| s_logger.info("Old " + server.getType().toString() | |
| + " host reconnected w/ id =" + server.getId()); | |
| } | |
| */ | |
| return true; | |
| } | |
| protected void updateSecondaryHost(final HostVO host, final StartupStorageCommand startup, final Host.Type type) throws AgentAuthnException { | |
| String zoneToken = startup.getDataCenter(); | |
| if (zoneToken == null) { | |
| s_logger.warn("No Zone Token passed in, cannot not find zone for the agent"); | |
| throw new AgentAuthnException("No Zone Token passed in, cannot not find zone for agent"); | |
| } | |
| DataCenterVO zone = _zoneDao.findByToken(zoneToken); | |
| if (zone == null) { | |
| zone = _zoneDao.findByName(zoneToken); | |
| if (zone == null) { | |
| try { | |
| long zoneId = Long.parseLong(zoneToken); | |
| zone = _zoneDao.findById(zoneId); | |
| if (zone == null) { | |
| throw new AgentAuthnException("Could not find zone for agent with token " + zoneToken); | |
| } | |
| } catch (NumberFormatException nfe) { | |
| throw new AgentAuthnException("Could not find zone for agent with token " + zoneToken); | |
| } | |
| } | |
| } | |
| if (s_logger.isDebugEnabled()) { | |
| s_logger.debug("Successfully loaded the DataCenter from the zone token passed in "); | |
| } | |
| HostPodVO pod = findPod(startup, zone.getId(), Host.Type.Routing); //yes, routing | |
| Long podId = null; | |
| if (pod != null) { | |
| s_logger.debug("Found pod " + pod.getName() + " for the secondary storage host " + startup.getName()); | |
| podId = pod.getId(); | |
| } | |
| host.setDataCenterId(zone.getId()); | |
| host.setPodId(podId); | |
| host.setClusterId(null); | |
| host.setPrivateIpAddress(startup.getPrivateIpAddress()); | |
| host.setPrivateNetmask(startup.getPrivateNetmask()); | |
| host.setPrivateMacAddress(startup.getPrivateMacAddress()); | |
| host.setPublicIpAddress(startup.getPublicIpAddress()); | |
| host.setPublicMacAddress(startup.getPublicMacAddress()); | |
| host.setPublicNetmask(startup.getPublicNetmask()); | |
| host.setStorageIpAddress(startup.getStorageIpAddress()); | |
| host.setStorageMacAddress(startup.getStorageMacAddress()); | |
| host.setStorageNetmask(startup.getStorageNetmask()); | |
| host.setVersion(startup.getVersion()); | |
| host.setName(startup.getName()); | |
| host.setType(type); | |
| host.setStorageUrl(startup.getIqn()); | |
| host.setLastPinged(System.currentTimeMillis() >> 10); | |
| host.setCaps(null); | |
| host.setCpus(null); | |
| host.setTotalMemory(0); | |
| host.setSpeed(null); | |
| host.setParent(startup.getParent()); | |
| host.setTotalSize(startup.getTotalSize()); | |
| host.setHypervisorType(HypervisorType.None); | |
| if (startup.getNfsShare() != null) { | |
| host.setStorageUrl(startup.getNfsShare()); | |
| } | |
| } | |
| private HostPodVO findPod(StartupCommand startup, long zoneId, Host.Type type) { | |
| HostPodVO pod = null; | |
| List<HostPodVO> podsInZone = _podDao.listByDataCenterId(zoneId); | |
| for (HostPodVO hostPod : podsInZone) { | |
| if (checkCIDR(type, hostPod, startup.getPrivateIpAddress(), startup.getPrivateNetmask())) { | |
| pod = hostPod; | |
| //found the default POD having the same subnet. | |
| updatePodNetmaskIfNeeded(pod, startup.getPrivateNetmask()); | |
| break; | |
| } | |
| } | |
| return pod; | |
| } | |
| } |