// 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.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.DetailVO; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.host.dao.HostDetailsDao; 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 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 private HostDetailsDao hostDetailsDao; @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 = Long.parseLong(maxHostsInZone.getValue()); 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()); updateHostDetails(host, scc); } private void updateHostDetails(HostVO host, StartupRoutingCommand startupRoutingCmd) { final String name = "supportsResign"; final String value = String.valueOf(startupRoutingCmd.getSupportsClonedVolumes()); DetailVO hostDetail = hostDetailsDao.findDetail(host.getId(), name); if (hostDetail != null) { hostDetail.setValue(value); hostDetailsDao.update(hostDetail.getId(), hostDetail); } else { hostDetail = new DetailVO(host.getId(), name, value); hostDetailsDao.persist(hostDetail); } } 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; } }