// 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.consoleproxy; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.agent.lb.IndirectAgentLB; import org.apache.cloudstack.ca.CAManager; import org.apache.cloudstack.consoleproxy.ConsoleAccessManager; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.ca.Certificate; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.security.keys.KeysManager; import org.apache.cloudstack.framework.security.keystore.KeystoreDao; import org.apache.cloudstack.framework.security.keystore.KeystoreManager; import org.apache.cloudstack.framework.security.keystore.KeystoreVO; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; import com.cloud.agent.api.ConsoleProxyLoadReportCommand; import com.cloud.agent.api.RebootCommand; import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupProxyCommand; import com.cloud.agent.api.check.CheckSshAnswer; import com.cloud.agent.api.check.CheckSshCommand; import com.cloud.agent.api.proxy.ConsoleProxyLoadAnswer; import com.cloud.agent.manager.Commands; import com.cloud.cluster.ClusterManager; import com.cloud.configuration.Config; import com.cloud.configuration.ConfigurationManagerImpl; import com.cloud.configuration.ZoneConfig; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter.NetworkType; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.HostPodDao; import com.cloud.deploy.DataCenterDeployment; import com.cloud.deploy.DeployDestination; import com.cloud.deploy.DeploymentPlanner; import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientAddressCapacityException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.host.Host.Type; import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.info.ConsoleProxyConnectionInfo; import com.cloud.info.ConsoleProxyInfo; import com.cloud.info.ConsoleProxyLoadInfo; import com.cloud.info.ConsoleProxyStatus; import com.cloud.info.RunningHostCountInfo; import com.cloud.info.RunningHostInfoAgregator; import com.cloud.info.RunningHostInfoAgregator.ZoneHostInfo; import com.cloud.network.Network; import com.cloud.network.NetworkModel; import com.cloud.network.Networks.TrafficType; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.rules.RulesManager; import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceStateAdapter; import com.cloud.resource.ServerResource; import com.cloud.resource.UnableDeleteHostException; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.Storage; import com.cloud.storage.StoragePoolStatus; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.utils.DateUtil; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; import com.cloud.utils.PasswordGenerator; import com.cloud.utils.StringUtils; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.DB; import com.cloud.utils.db.GlobalLock; import com.cloud.utils.db.QueryBuilder; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.events.SubscriptionMgr; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.ConsoleProxyVO; import com.cloud.vm.NicProfile; import com.cloud.vm.ReservationContext; import com.cloud.vm.SystemVmLoadScanHandler; import com.cloud.vm.SystemVmLoadScanner; import com.cloud.vm.SystemVmLoadScanner.AfterScanAction; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.VirtualMachineGuru; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VirtualMachineName; import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.dao.ConsoleProxyDao; import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; /** * Class to manage console proxys.

* Possible console proxy state transition cases:
* - Stopped -> Starting -> Running
* - HA -> Stopped -> Starting -> Running
* - Migrating -> Running (if previous state is Running before it enters into Migrating state)
* - Migrating -> Stopped (if previous state is not Running before it enters into Migrating state)
* - Running -> HA (if agent lost connection)
* - Stopped -> Destroyed
* * Starting, HA, Migrating and Running states are all counted as Open for available capacity calculation because sooner or later, it will be driven into Running state **/ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxyManager, VirtualMachineGuru, SystemVmLoadScanHandler, ResourceStateAdapter, Configurable { private static final int DEFAULT_CAPACITY_SCAN_INTERVAL_IN_MILLISECONDS = 30000; private static final int ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_SYNC_IN_SECONDS = 180; private static final int STARTUP_DELAY_IN_MILLISECONDS = 60000; private int consoleProxyPort = ConsoleProxyManager.DEFAULT_PROXY_VNC_PORT; private int managementPort = 8250; private List consoleProxyAllocators; @Inject private ConsoleProxyDao consoleProxyDao; @Inject private DataCenterDao dataCenterDao; @Inject private VMTemplateDao vmTemplateDao; @Inject private HostPodDao hostPodDao; @Inject private HostDao hostDao; @Inject private ConfigurationDao configurationDao; @Inject private VMInstanceDao vmInstanceDao; @Inject private TemplateDataStoreDao templateDataStoreDao; @Inject private AgentManager agentManager; @Inject private NetworkOrchestrationService networkOrchestrationService; @Inject private NetworkModel networkModel; @Inject private AccountManager accountManager; @Inject private ServiceOfferingDao serviceOfferingDao; @Inject private NetworkOfferingDao networkOfferingDao; @Inject private PrimaryDataStoreDao primaryDataStoreDao; @Inject private VMInstanceDetailsDao vmInstanceDetailsDao; @Inject private ResourceManager resourceManager; @Inject private NetworkDao networkDao; @Inject private RulesManager rulesManager; @Inject private IPAddressDao ipAddressDao; @Inject private KeysManager keysManager; @Inject private VirtualMachineManager virtualMachineManager; @Inject private IndirectAgentLB indirectAgentLB; @Inject private CAManager caManager; @Inject private NetworkOrchestrationService networkMgr; private ConsoleProxyListener consoleProxyListener; private long capacityScanInterval = DEFAULT_CAPACITY_SCAN_INTERVAL_IN_MILLISECONDS; private boolean useStorageVm; private String instance; private SystemVmLoadScanner loadScanner; private Map zoneHostInfoMap; private Map zoneProxyCountMap; private Map zoneVmCountMap; private String staticPublicIp; private int staticPort; private final GlobalLock allocProxyLock = GlobalLock.getInternLock(getAllocProxyLockName()); protected Gson jsonParser = new GsonBuilder().setVersion(1.3).create(); protected Set availableVmStateOnAssignProxy = new HashSet<>(Arrays.asList(State.Starting, State.Running, State.Stopping, State.Migrating)); @Inject private KeystoreDao _ksDao; @Inject private KeystoreManager _ksMgr; @Inject private ConsoleAccessManager consoleAccessManager; public class VmBasedAgentHook extends AgentHookBase { public VmBasedAgentHook(VMInstanceDao instanceDao, HostDao hostDao, ConfigurationDao cfgDao, KeystoreManager ksMgr, AgentManager agentMgr, KeysManager keysMgr, ConsoleAccessManager consoleAccessManager) { super(instanceDao, hostDao, cfgDao, ksMgr, agentMgr, keysMgr, consoleAccessManager); } @Override public void onLoadReport(ConsoleProxyLoadReportCommand cmd) { updateConsoleProxyStatus(cmd.getLoadInfo(), cmd.getProxyVmId()); } @Override public void onAgentDisconnect(long agentId, Status state) { if (state == Status.Alert || state == Status.Disconnected) { HostVO host = _hostDao.findById(agentId); if (host.getType() == Type.ConsoleProxy) { String name = host.getName(); if (logger.isInfoEnabled()) { logger.info("Console proxy agent disconnected, proxy: " + name); } if (name != null && name.startsWith("v-")) { String[] tokens = name.split("-"); long proxyVmId; String tokenSecondElement = tokens[1]; try { proxyVmId = Long.parseLong(tokenSecondElement); } catch (NumberFormatException e) { logger.error(String.format("[%s] is not a valid number, unable to parse [%s].", tokenSecondElement, e.getMessage()), e); return; } final ConsoleProxyVO proxy = consoleProxyDao.findById(proxyVmId); if (proxy == null && logger.isInfoEnabled()) { logger.info("Console proxy agent disconnected but corresponding console proxy VM no longer exists in DB, proxy: " + name); } } else { assert (false) : "Invalid console proxy name: " + name; } } } } @Override protected HostVO findConsoleProxyHost(StartupProxyCommand startupCmd) { long proxyVmId = startupCmd.getProxyVmId(); ConsoleProxyVO consoleProxy = consoleProxyDao.findById(proxyVmId); if (consoleProxy == null) { logger.info("Proxy " + proxyVmId + " is no longer in DB, skip sending startup command"); return null; } assert (consoleProxy != null); return findConsoleProxyHostByName(consoleProxy.getHostName()); } } @Override public ConsoleProxyInfo assignProxy(final long dataCenterId, final VMInstanceVO userVm) { ConsoleProxyVO proxy = doAssignProxy(dataCenterId, userVm); if (proxy == null) { return null; } if (proxy.getPublicIpAddress() == null) { logger.warn(String.format("Assigned console proxy [%s] does not have a valid public IP address.", proxy.toString())); return null; } KeystoreVO ksVo = _ksDao.findByName(ConsoleProxyManager.CERTIFICATE_NAME); if (proxy.isSslEnabled() && ksVo == null) { logger.warn(String.format("SSL is enabled for console proxy [%s] but no server certificate found in database.", proxy.toString())); } String consoleProxyUrlDomain = ConsoleProxyUrlDomain.valueIn(dataCenterId); ConsoleProxyInfo info; if (staticPublicIp == null) { info = new ConsoleProxyInfo(proxy.isSslEnabled(), proxy.getPublicIpAddress(), consoleProxyPort, proxy.getPort(), consoleProxyUrlDomain); } else { info = new ConsoleProxyInfo(proxy.isSslEnabled(), staticPublicIp, consoleProxyPort, staticPort, consoleProxyUrlDomain); } info.setProxyName(proxy.getHostName()); return info; } public ConsoleProxyVO doAssignProxy(long dataCenterId, VMInstanceVO vm) { ConsoleProxyVO proxy = null; if (!availableVmStateOnAssignProxy.contains(vm.getState())) { if (logger.isInfoEnabled()) { logger.info(String.format("Detected that %s is not currently in \"Starting\", \"Running\", \"Stopping\" or \"Migrating\" state, it will fail the proxy assignment.", vm.toString())); } return null; } if (allocProxyLock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_SYNC_IN_SECONDS)) { try { if (vm.getProxyId() != null) { proxy = consoleProxyDao.findById(vm.getProxyId()); if (proxy != null) { if (!isInAssignableState(proxy)) { if (logger.isInfoEnabled()) { logger.info("A previous assigned proxy is not assignable now, reassign console proxy for user vm : {}", vm); } proxy = null; } else { long capacityPerProxy = ConsoleProxySessionMax.valueIn(dataCenterId); if (consoleProxyDao.getProxyActiveLoad(proxy.getId()) < capacityPerProxy || hasPreviousSession(proxy, vm)) { if (logger.isDebugEnabled()) { logger.debug("Assign previous allocated console proxy for user vm: {}", vm); } if (proxy.getActiveSession() >= capacityPerProxy) { logger.warn("Assign overloaded proxy to user VM as previous session exists, user vm: {}", vm); } } else { proxy = null; } } } } if (proxy == null) { proxy = assignProxyFromRunningPool(dataCenterId); } } finally { allocProxyLock.unlock(); } } else { logger.error("Unable to acquire synchronization lock to get/allocate proxy " + "resource for vm: {}. Previous console proxy allocation is taking too long", vm); } if (proxy == null) { logger.warn("Unable to find or allocate console proxy resource"); return null; } if (vm.getProxyId() == null || vm.getProxyId() != proxy.getId()) { vmInstanceDao.updateProxyId(vm.getId(), proxy.getId(), DateUtil.currentGMTTime()); } boolean sslEnabled = isSslEnabled(dataCenterId); proxy.setSslEnabled(sslEnabled); if (sslEnabled) { proxy.setPort(443); } else { proxy.setPort(80); } return proxy; } private static boolean isInAssignableState(ConsoleProxyVO proxy) { return proxy.getState() == State.Running; } private boolean hasPreviousSession(ConsoleProxyVO proxy, VMInstanceVO vm) { ConsoleProxyStatus status = null; try { byte[] detailsInBytes = proxy.getSessionDetails(); String details = detailsInBytes != null ? new String(detailsInBytes, Charset.forName("US-ASCII")) : null; status = parseJsonToConsoleProxyStatus(details); } catch (JsonParseException e) { logger.warn(String.format("Unable to parse proxy [%s] session details [%s] due to [%s].", proxy.toString(), Arrays.toString(proxy.getSessionDetails()), e.getMessage()), e); } if (status != null && status.getConnections() != null) { ConsoleProxyConnectionInfo[] connections = status.getConnections(); for (ConsoleProxyConnectionInfo connection : connections) { long taggedVmId = 0; if (connection.tag != null) { try { taggedVmId = Long.parseLong(connection.tag); } catch (NumberFormatException e) { logger.warn(String.format("Unable to parse console proxy connection info passed through tag [%s] due to [%s].", connection.tag, e.getMessage()), e); } } if (taggedVmId == vm.getId()) { return true; } } Integer proxySessionTimeoutValue = ConsoleProxySessionTimeout.valueIn(proxy.getDataCenterId()); return DateUtil.currentGMTTime().getTime() - vm.getProxyAssignTime().getTime() < proxySessionTimeoutValue; } else { logger.warn(String.format("Unable to retrieve load info from proxy [%s] on an overloaded proxy.", proxy.toString())); return false; } } @Override public ConsoleProxyVO startProxy(long proxyVmId, boolean ignoreRestartSetting) { try { ConsoleProxyVO proxy = consoleProxyDao.findById(proxyVmId); if (proxy.getState() == State.Running) { return proxy; } Boolean restart = ConsoleProxyRestart.valueIn(proxy.getDataCenterId()); if (!ignoreRestartSetting && Boolean.FALSE.equals(restart)) { return null; } if (proxy.getState() == State.Stopped) { virtualMachineManager.advanceStart(proxy.getUuid(), null, null); proxy = consoleProxyDao.findById(proxy.getId()); return proxy; } logger.warn(String.format("Console proxy [%s] must be in \"Stopped\" state to start proxy. Current state [%s].", proxy.toString(), proxy.getState())); } catch ( ConcurrentOperationException | InsufficientCapacityException | OperationTimedoutException | ResourceUnavailableException ex) { logger.warn(String.format("Unable to start proxy [%s] due to [%s].", proxyVmId, ex.getMessage()), ex); } return null; } @Override @ActionEvent(eventType = EventTypes.EVENT_PROXY_START, eventDescription = "restarting console proxy VM for HA", async = true) public void startProxyForHA(VirtualMachine vm, Map params, DeploymentPlanner planner) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, OperationTimedoutException { virtualMachineManager.advanceStart(vm.getUuid(), params, planner); } public ConsoleProxyVO assignProxyFromRunningPool(long dataCenterId) { DataCenterVO zone = dataCenterDao.findById(dataCenterId); if (logger.isDebugEnabled()) { logger.debug("Assign console proxy from running pool for request from data center: {}", zone); } long capacityPerProxy = ConsoleProxySessionMax.valueIn(dataCenterId); ConsoleProxyAllocator allocator = getCurrentAllocator(); assert (allocator != null); List runningList = consoleProxyDao.getProxyListInStates(dataCenterId, State.Running); if (runningList != null && runningList.size() > 0) { Iterator it = runningList.iterator(); while (it.hasNext()) { ConsoleProxyVO proxy = it.next(); if (proxy.getActiveSession() >= capacityPerProxy) { it.remove(); } } if (logger.isDebugEnabled()) { logger.debug(String.format("Running [%s] proxy instances [%s].", runningList.size(), runningList.stream().map(proxy -> proxy.toString()).collect(Collectors.joining(", ")))); } List> l = consoleProxyDao.getProxyLoadMatrix(); Map loadInfo = new HashMap<>(); if (l != null) { for (Pair p : l) { Long proxyId = p.first(); Integer countRunningVms = p.second(); loadInfo.put(proxyId, countRunningVms); if (logger.isDebugEnabled()) { logger.debug(String.format("Running proxy instance allocation {\"proxyId\": %s, \"countRunningVms\": %s}.", proxyId, countRunningVms)); } } } Long allocated = allocator.allocProxy(runningList, loadInfo, dataCenterId); if (allocated == null) { logger.debug("Console proxy not found, unable to assign console proxy from running pool for request from zone [{}].", zone); return null; } return consoleProxyDao.findById(allocated); } else { if (logger.isDebugEnabled()) { logger.debug("Empty running proxy pool for now in data center: {}", zone); } } return null; } public ConsoleProxyVO assignProxyFromStoppedPool(long dataCenterId) { List l = consoleProxyDao.getProxyListInStates(dataCenterId, State.Starting, State.Stopped, State.Migrating, State.Stopping); if (CollectionUtils.isNotEmpty(l)) { return l.get(0); } return null; } public ConsoleProxyVO startNew(long dataCenterId) throws ConcurrentOperationException, ConfigurationException { if (logger.isDebugEnabled()) { logger.debug("Assign console proxy from a newly started instance for request from data center : " + dataCenterId); } if (!allowToLaunchNew(dataCenterId)) { String configKey = ConsoleProxyLaunchMax.key(); Integer configValue = ConsoleProxyLaunchMax.valueIn(dataCenterId); logger.warn(String.format("The number of launched console proxys on zone [%s] has reached the limit [%s]. Limit set in [%s].", dataCenterId, configValue, configKey)); return null; } HypervisorType availableHypervisor = resourceManager.getAvailableHypervisor(dataCenterId); List templates = vmTemplateDao.findSystemVMReadyTemplates(dataCenterId, availableHypervisor, ResourceManager.SystemVmPreferredArchitecture.valueIn(dataCenterId)); if (CollectionUtils.isEmpty(templates)) { throw new CloudRuntimeException("Not able to find the System templates or not downloaded in zone " + dataCenterId); } Map context = createProxyInstance(dataCenterId, templates); long proxyVmId = (Long)context.get("proxyVmId"); if (proxyVmId == 0) { if (logger.isDebugEnabled()) { logger.debug(String.format("Unable to create proxy instance in zone [%s].", dataCenterId)); } return null; } ConsoleProxyVO proxy = consoleProxyDao.findById(proxyVmId); if (proxy != null) { SubscriptionMgr.getInstance().notifySubscribers(ConsoleProxyManager.ALERT_SUBJECT, this, new ConsoleProxyAlertEventArgs(ConsoleProxyAlertEventArgs.PROXY_CREATED, dataCenterId, proxy.getId(), proxy, null)); return proxy; } else { if (logger.isDebugEnabled()) { logger.debug("Unable to allocate console proxy storage, remove the console proxy record from DB, proxy id: " + proxyVmId); } } return null; } /** * Get the default network for the console proxy VM, based on the zone it is in. Delegates to * either {@link #getDefaultNetworkForZone(DataCenter)} or {@link #getDefaultNetworkForAdvancedSGZone(DataCenter)}, * depending on the zone network type and whether or not security groups are enabled in the zone. * @param dc - The zone (DataCenter) of the console proxy VM. * @return The default network for use with the console proxy VM. */ protected NetworkVO getDefaultNetworkForCreation(DataCenter dc) { if (dc.getNetworkType() == NetworkType.Advanced) { return getDefaultNetworkForAdvancedZone(dc); } else { return getDefaultNetworkForBasicZone(dc); } } /** * Get default network for a console proxy VM starting up in an advanced zone. If the zone * is security group-enabled, the first network found that supports SG services is returned. * If the zone is not SG-enabled, the Public network is returned. * @param dc - The zone. * @return The selected default network. * @throws CloudRuntimeException - If the zone is not a valid choice or a network couldn't be found. */ protected NetworkVO getDefaultNetworkForAdvancedZone(DataCenter dc) { if (dc.getNetworkType() != NetworkType.Advanced) { throw new CloudRuntimeException("Zone " + dc + " is not advanced."); } if (dc.isSecurityGroupEnabled()) { List networks = networkDao.listByZoneSecurityGroup(dc.getId()); if (CollectionUtils.isEmpty(networks)) { throw new CloudRuntimeException("Can not found security enabled network in SG Zone " + dc); } return networks.get(0); } else { TrafficType defaultTrafficType = TrafficType.Public; List defaultNetworks = networkDao.listByZoneAndTrafficType(dc.getId(), defaultTrafficType); if (defaultNetworks.size() != 1) { throw new CloudRuntimeException("Found " + defaultNetworks.size() + " networks of type " + defaultTrafficType + " when expect to find 1"); } return defaultNetworks.get(0); } } /** * Get default network for console proxy VM for starting up in a basic zone. Basic zones select * the Guest network whether or not the zone is SG-enabled. * @param dc - The zone. * @return The default network according to the zone's network selection rules. * @throws CloudRuntimeException - If the zone is not a valid choice or a network couldn't be found. */ protected NetworkVO getDefaultNetworkForBasicZone(DataCenter dc) { if (dc.getNetworkType() != NetworkType.Basic) { throw new CloudRuntimeException("Zone " + dc + "is not basic."); } TrafficType defaultTrafficType = TrafficType.Guest; List defaultNetworks = networkDao.listByZoneAndTrafficType(dc.getId(), defaultTrafficType); if (defaultNetworks.size() != 1) { throw new CloudRuntimeException("Found " + defaultNetworks.size() + " networks of type " + defaultTrafficType + " when expect to find 1"); } return defaultNetworks.get(0); } protected ConsoleProxyVO createOrUpdateConsoleProxy(ConsoleProxyVO proxy, long dataCenterId, long id, String name, ServiceOffering serviceOffering, VMTemplateVO template, Account systemAccount) { if (proxy == null) { proxy = new ConsoleProxyVO(id, serviceOffering.getId(), name, template.getId(), template.getHypervisorType(), template.getGuestOSId(), dataCenterId, systemAccount.getDomainId(), systemAccount.getId(), accountManager.getSystemUser().getId(), 0, serviceOffering.isOfferHA()); proxy.setDynamicallyScalable(template.isDynamicallyScalable()); proxy.setLimitCpuUse(serviceOffering.getLimitCpuUse()); return consoleProxyDao.persist(proxy); } proxy.setTemplateId(template.getId()); proxy.setHypervisorType(template.getHypervisorType()); proxy.setGuestOSId(template.getGuestOSId()); proxy.setDynamicallyScalable(template.isDynamicallyScalable()); consoleProxyDao.update(proxy.getId(), proxy); return proxy; } protected Map createProxyInstance(long dataCenterId, List templates) throws ConcurrentOperationException, ConfigurationException { long id = consoleProxyDao.getNextInSequence(Long.class, "id"); String name = VirtualMachineName.getConsoleProxyName(id, instance); DataCenterVO dc = dataCenterDao.findById(dataCenterId); Account systemAcct = accountManager.getSystemAccount(); DataCenterDeployment plan = new DataCenterDeployment(dataCenterId); NetworkVO defaultNetwork = getDefaultNetworkForCreation(dc); List offerings = networkModel.getSystemAccountNetworkOfferings(NetworkOffering.SystemControlNetwork, NetworkOffering.SystemManagementNetwork); LinkedHashMap> networks = new LinkedHashMap<>(offerings.size() + 1); NicProfile defaultNic = new NicProfile(); defaultNic.setDefaultNic(true); defaultNic.setDeviceId(2); networks.put(networkOrchestrationService.setupNetwork(systemAcct, networkOfferingDao.findById(defaultNetwork.getNetworkOfferingId()), plan, null, null, false).get(0), new ArrayList<>(Arrays.asList(defaultNic))); for (NetworkOffering offering : offerings) { networks.put(networkOrchestrationService.setupNetwork(systemAcct, offering, plan, null, null, false).get(0), new ArrayList<>()); } ServiceOfferingVO serviceOffering = getConsoleProxyServiceOffering(dataCenterId); if (serviceOffering == null) { serviceOffering = serviceOfferingDao.findDefaultSystemOffering(ServiceOffering.consoleProxyDefaultOffUniqueName, ConfigurationManagerImpl.SystemVMUseLocalStorage.valueIn(dataCenterId)); } ConsoleProxyVO proxy = null; for (final Iterator templateIterator = templates.iterator(); templateIterator.hasNext();) { VMTemplateVO template = templateIterator.next(); proxy = createOrUpdateConsoleProxy(proxy, dataCenterId, id, name, serviceOffering, template, systemAcct); try { virtualMachineManager.allocate(name, template, serviceOffering, networks, plan, template.getHypervisorType(), null, null); proxy = consoleProxyDao.findById(proxy.getId()); virtualMachineManager.checkDeploymentPlan(proxy, template, serviceOffering, systemAcct, plan); break; } catch (InsufficientCapacityException e) { if (templateIterator.hasNext()) { logger.debug("Unable to allocate proxy {} with {} in {} due to [{}]. Retrying with another template", proxy, template, dc, e.getMessage(), e); continue; } throw new CloudRuntimeException("Failed to allocate proxy [%s] in zone [%s] with available templates", e); } } Map context = new HashMap<>(); context.put("dc", dc); HostPodVO pod = hostPodDao.findById(proxy.getPodIdToDeployIn()); context.put("pod", pod); context.put("proxyVmId", proxy.getId()); return context; } private ConsoleProxyAllocator getCurrentAllocator() { for (ConsoleProxyAllocator allocator : consoleProxyAllocators) { return allocator; } return null; } public void onLoadAnswer(ConsoleProxyLoadAnswer answer) { updateConsoleProxyStatus(answer.getDetails(), answer.getProxyVmId()); } public void handleAgentDisconnect(long agentId, Status state) { if (state == Status.Alert || state == Status.Disconnected) { HostVO host = hostDao.findById(agentId); if (host.getType() == Type.ConsoleProxy) { String name = host.getName(); if (logger.isInfoEnabled()) { logger.info("Console proxy agent disconnected, proxy: " + name); } if (name != null && name.startsWith("v-")) { String[] tokens = name.split("-"); long proxyVmId; try { proxyVmId = Long.parseLong(tokens[1]); } catch (NumberFormatException e) { logger.error("Unexpected exception " + e.getMessage(), e); return; } final ConsoleProxyVO proxy = consoleProxyDao.findById(proxyVmId); if (proxy == null && logger.isInfoEnabled()) { logger.info("Console proxy agent disconnected but corresponding console proxy VM no longer exists in DB, proxy: " + name); } } else { assert (false) : "Invalid console proxy name: " + name; } } } } private boolean reserveStandbyCapacity() { ConsoleProxyManagementState state = getManagementState(); return !(state == null || state != ConsoleProxyManagementState.Auto); } private boolean isConsoleProxyVmRequired(long dcId) { DataCenterVO dc = dataCenterDao.findById(dcId); dataCenterDao.loadDetails(dc); String cpvmReq = dc.getDetail(ZoneConfig.EnableConsoleProxyVm.key()); if (cpvmReq != null) { return Boolean.parseBoolean(cpvmReq); } return true; } private boolean allowToLaunchNew(long dcId) { if (!isConsoleProxyVmRequired(dcId)) { if (logger.isDebugEnabled()) { logger.debug("Console proxy vm not required in zone " + dcId + " not launching"); } return false; } List l = consoleProxyDao.getProxyListInStates(dcId, State.Starting, State.Running, State.Stopping, State.Stopped, State.Migrating, State.Shutdown, State.Unknown); int launchLimit = ConsoleProxyLaunchMax.valueIn(dcId); return l.size() < launchLimit; } private boolean checkCapacity(ConsoleProxyLoadInfo proxyCountInfo, ConsoleProxyLoadInfo vmCountInfo, long dataCenterId) { long capacityPerProxy = ConsoleProxySessionMax.valueIn(dataCenterId); return proxyCountInfo.getCount() * capacityPerProxy - vmCountInfo.getCount() > getStandbyCapacity(dataCenterId); } private void allocCapacity(long dataCenterId) { DataCenterVO zone = dataCenterDao.findById(dataCenterId); if (logger.isDebugEnabled()) { logger.debug("Allocating console proxy standby capacity for zone [{}].", zone); } ConsoleProxyVO proxy = null; String errorString = null; try { boolean consoleProxyVmFromStoppedPool = false; proxy = assignProxyFromStoppedPool(dataCenterId); if (proxy == null) { if (logger.isInfoEnabled()) { logger.info("No stopped console proxy is available, need to allocate a new console proxy"); } if (allocProxyLock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_SYNC_IN_SECONDS)) { try { proxy = startNew(dataCenterId); } catch (ConcurrentOperationException | ConfigurationException e) { logger.warn("Unable to start new console proxy on zone [{}] due to [{}].", zone, e.getMessage(), e); } finally { allocProxyLock.unlock(); } } else { if (logger.isInfoEnabled()) { logger.info("Unable to acquire synchronization lock for console proxy vm allocation, wait for next scan"); } } } else { if (logger.isInfoEnabled()) { logger.info("Found a stopped console proxy, starting it. VM: {}", proxy); } consoleProxyVmFromStoppedPool = true; } if (proxy != null) { long proxyVmId = proxy.getId(); proxy = startProxy(proxyVmId, false); if (proxy != null) { if (logger.isInfoEnabled()) { logger.info("Console proxy {} is started", proxy); } SubscriptionMgr.getInstance().notifySubscribers(ConsoleProxyManager.ALERT_SUBJECT, this, new ConsoleProxyAlertEventArgs(ConsoleProxyAlertEventArgs.PROXY_UP, dataCenterId, proxy.getId(), proxy, null)); } else { if (logger.isInfoEnabled()) { logger.info("Unable to start console proxy vm for standby capacity, vm: {}, will recycle it and start a new one", proxy); } if (consoleProxyVmFromStoppedPool) { destroyProxy(proxyVmId); } } } } catch (Exception e) { errorString = e.getMessage(); logger.warn("Unable to allocate console proxy standby capacity for zone [{}] due to [{}].", zone, e.getMessage(), e); throw e; } finally { if (proxy == null || proxy.getState() != State.Running) SubscriptionMgr.getInstance().notifySubscribers(ConsoleProxyManager.ALERT_SUBJECT, this, new ConsoleProxyAlertEventArgs(ConsoleProxyAlertEventArgs.PROXY_CREATE_FAILURE, dataCenterId, 0l, null, errorString)); } } public boolean isZoneReady(Map zoneHostInfoMap, DataCenter dataCenter) { Integer totalUpAndEnabledHosts = hostDao.countUpAndEnabledHostsInZone(dataCenter.getId()); if (totalUpAndEnabledHosts != null && totalUpAndEnabledHosts < 1) { logger.debug("{} has no host available which is enabled and in Up state", dataCenter); return false; } ZoneHostInfo zoneHostInfo = zoneHostInfoMap.get(dataCenter.getId()); if (zoneHostInfo != null && isZoneHostReady(zoneHostInfo)) { List templates = vmTemplateDao.findSystemVMReadyTemplates(dataCenter.getId(), HypervisorType.Any, null); if (CollectionUtils.isEmpty(templates)) { if (logger.isDebugEnabled()) { logger.debug("System vm template is not ready at data center {}, wait until it is ready to launch console proxy vm", dataCenter); } return false; } boolean useLocalStorage = BooleanUtils.toBoolean(ConfigurationManagerImpl.SystemVMUseLocalStorage.valueIn(dataCenter.getId())); boolean hasDatacenterStoragePoolHostInfo = consoleProxyDao.hasDatacenterStoragePoolHostInfo(dataCenter.getId(), !useLocalStorage); if (hasDatacenterStoragePoolHostInfo) { return true; } else { if (logger.isDebugEnabled()) { logger.debug("Primary storage is not ready, wait until it is ready to launch console proxy"); } } } return false; } private boolean isZoneHostReady(ZoneHostInfo zoneHostInfo) { int expectedFlags; if (useStorageVm) { expectedFlags = ZoneHostInfo.ROUTING_HOST_MASK; } else { expectedFlags = ZoneHostInfo.ALL_HOST_MASK; } return (zoneHostInfo.getFlags() & expectedFlags) == expectedFlags; } private synchronized Map getZoneHostInfo() { Date cutTime = DateUtil.currentGMTTime(); List l = hostDao.getRunningHostCounts(new Date(cutTime.getTime() - ClusterManager.HeartbeatThreshold.value())); RunningHostInfoAgregator aggregator = new RunningHostInfoAgregator(); if (l.size() > 0) { for (RunningHostCountInfo countInfo : l) { aggregator.aggregate(countInfo); } } return aggregator.getZoneHostInfoMap(); } @Override public boolean start() { if (logger.isInfoEnabled()) { logger.info("Start console proxy manager"); } return true; } @Override public boolean stop() { if (logger.isInfoEnabled()) { logger.info("Stop console proxy manager"); } loadScanner.stop(); allocProxyLock.releaseRef(); resourceManager.unregisterResourceStateAdapter(this.getClass().getSimpleName()); return true; } @Override public boolean stopProxy(long proxyVmId) { ConsoleProxyVO proxy = consoleProxyDao.findById(proxyVmId); if (proxy == null) { if (logger.isDebugEnabled()) { logger.debug("Stopping console proxy failed: console proxy " + proxyVmId + " no longer exists"); } return false; } try { virtualMachineManager.stop(proxy.getUuid()); return true; } catch (CloudRuntimeException | ResourceUnavailableException e) { logger.warn(String.format("Unable to stop console proxy [%s] due to [%s].", proxy.toString(), e.getMessage()), e); return false; } } @Override @DB public void setManagementState(final ConsoleProxyManagementState state) { try { final ConsoleProxyManagementState lastState = getManagementState(); if (lastState == null) { return; } if (lastState != state) { Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { configurationDao.update(ConsoleProxyManagementLastState.key(), ConsoleProxyManagementLastState.category(), lastState.toString()); configurationDao.update(ConsoleProxyServiceManagementState.key(), ConsoleProxyServiceManagementState.category(), state.toString()); } }); } } catch (Exception e) { logger.error(String.format("Unable to set console proxy management state to [%s] due to [%s].", state, e.getMessage()), e); } } @Override public ConsoleProxyManagementState getManagementState() { String configKey = ConsoleProxyServiceManagementState.key(); String value = ConsoleProxyServiceManagementState.value(); if (value != null) { ConsoleProxyManagementState state = ConsoleProxyManagementState.valueOf(value); if (state != null) { return state; } } logger.error(String.format("Value [%s] set in global configuration [%s] is not a valid console proxy management state.", value, configKey)); return null; } @Override @DB public void resumeLastManagementState() { try { ConsoleProxyManagementState state = getManagementState(); ConsoleProxyManagementState lastState = getLastManagementState(); if (lastState == null) { return; } if (lastState != state) { configurationDao.update(ConsoleProxyServiceManagementState.key(), ConsoleProxyServiceManagementState.category(), lastState.toString()); } } catch (Exception e) { logger.error(String.format("Unable to resume last management state due to [%s].", e.getMessage()), e); } } private ConsoleProxyManagementState getLastManagementState() { String configKey = ConsoleProxyManagementLastState.key(); String value = ConsoleProxyManagementLastState.value(); if (value != null) { ConsoleProxyManagementState state = ConsoleProxyManagementState.valueOf(value); if (state != null) { return state; } } logger.error(String.format("Value [%s] set in global configuration [%s] is not a valid console proxy management state.", value, configKey)); return null; } @Override public boolean rebootProxy(long proxyVmId) { final ConsoleProxyVO proxy = consoleProxyDao.findById(proxyVmId); if (proxy == null || proxy.getState() == State.Destroyed) { return false; } if (proxy.getState() == State.Running && proxy.getHostId() != null) { final RebootCommand cmd = new RebootCommand(proxy.getInstanceName(), virtualMachineManager.getExecuteInSequence(proxy.getHypervisorType())); final Answer answer = agentManager.easySend(proxy.getHostId(), cmd); if (answer != null && answer.getResult()) { if (logger.isDebugEnabled()) { logger.debug("Successfully reboot console proxy " + proxy.getHostName()); } SubscriptionMgr.getInstance().notifySubscribers(ConsoleProxyManager.ALERT_SUBJECT, this, new ConsoleProxyAlertEventArgs(ConsoleProxyAlertEventArgs.PROXY_REBOOTED, proxy.getDataCenterId(), proxy.getId(), proxy, null)); return true; } else { if (logger.isDebugEnabled()) { logger.debug("failed to reboot console proxy : " + proxy.getHostName()); } return false; } } else { return startProxy(proxyVmId, false) != null; } } @Override public boolean destroyProxy(long vmId) { ConsoleProxyVO proxy = consoleProxyDao.findById(vmId); try { virtualMachineManager.expunge(proxy.getUuid()); proxy.setPublicIpAddress(null); proxy.setPublicMacAddress(null); proxy.setPublicNetmask(null); proxy.setPrivateMacAddress(null); proxy.setPrivateIpAddress(null); consoleProxyDao.update(proxy.getId(), proxy); consoleProxyDao.remove(vmId); HostVO host = hostDao.findByTypeNameAndZoneId(proxy.getDataCenterId(), proxy.getHostName(), Type.ConsoleProxy); if (host != null) { logger.debug("Removing host [{}] entry for proxy [{}].", host, proxy); return hostDao.remove(host.getId()); } return true; } catch (ResourceUnavailableException e) { logger.warn(String.format("Unable to destroy console proxy [%s] due to [%s].", proxy, e.getMessage()), e); return false; } } @Override public int getVncPort(Long dataCenterId) { return isSslEnabled(dataCenterId) && _ksDao.findByName(ConsoleProxyManager.CERTIFICATE_NAME) != null ? 8443 : 8080; } private String getAllocProxyLockName() { return "consoleproxy.alloc"; } @Override public boolean configure(String name, Map params) throws ConfigurationException { if (logger.isInfoEnabled()) { logger.info("Start configuring console proxy manager : " + name); } Map configs = configurationDao.getConfiguration("management-server", params); String value = ConsoleProxyCapacityScanInterval.value(); capacityScanInterval = NumbersUtil.parseLong(value, DEFAULT_CAPACITY_SCAN_INTERVAL_IN_MILLISECONDS); value = configs.get("consoleproxy.port"); if (value != null) { consoleProxyPort = NumbersUtil.parseInt(value, ConsoleProxyManager.DEFAULT_PROXY_VNC_PORT); } value = configs.get("secondary.storage.vm"); if (value != null && value.equalsIgnoreCase("true")) { useStorageVm = true; } instance = configs.get("instance.name"); if (instance == null) { instance = "DEFAULT"; } Map agentMgrConfigs = configurationDao.getConfiguration("AgentManager", params); value = agentMgrConfigs.get("port"); managementPort = NumbersUtil.parseInt(value, 8250); consoleProxyListener = new ConsoleProxyListener(new VmBasedAgentHook(vmInstanceDao, hostDao, configurationDao, _ksMgr, agentManager, keysManager, consoleAccessManager)); agentManager.registerForHostEvents(consoleProxyListener, true, true, false); virtualMachineManager.registerGuru(VirtualMachine.Type.ConsoleProxy, this); loadScanner = new SystemVmLoadScanner<>(this); loadScanner.initScan(STARTUP_DELAY_IN_MILLISECONDS, capacityScanInterval); resourceManager.registerResourceStateAdapter(this.getClass().getSimpleName(), this); staticPublicIp = configurationDao.getValue("consoleproxy.static.publicIp"); if (staticPublicIp != null) { staticPort = NumbersUtil.parseInt(configurationDao.getValue("consoleproxy.static.port"), 8443); } if (logger.isInfoEnabled()) { logger.info("Console Proxy Manager is configured."); } return true; } protected ConsoleProxyManagerImpl() { } @Override public boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) { final Map sshAccessDetails = networkMgr.getSystemVMAccessDetails(profile.getVirtualMachine()); final Map ipAddressDetails = new HashMap<>(sshAccessDetails); ipAddressDetails.remove("router.name"); final Certificate certificate = caManager.issueCertificate(null, Arrays.asList(profile.getHostName(), profile.getInstanceName()), new ArrayList<>(ipAddressDetails.values()), CAManager.CertValidityPeriod.value(), null); ConsoleProxyVO vm = consoleProxyDao.findById(profile.getId()); Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId()); vm.setDetails(details); StringBuilder buf = profile.getBootArgsBuilder(); buf.append(" template=domP type=consoleproxy"); buf.append(" host=").append(StringUtils.toCSVList(indirectAgentLB.getManagementServerList(dest.getHost().getId(), dest.getDataCenter().getId(), null))); buf.append(" port=").append(managementPort); buf.append(" name=").append(profile.getVirtualMachine().getHostName()); if (isSslEnabled(dest.getDataCenter().getId())) { buf.append(" premium=true"); } Long datacenterId = dest.getDataCenter().getId(); buf.append(" zone=").append(datacenterId); buf.append(" pod=").append(dest.getPod().getId()); buf.append(" guid=Proxy.").append(profile.getId()); buf.append(" proxy_vm=").append(profile.getId()); Boolean disableRpFilter = ConsoleProxyDisableRpFilter.valueIn(datacenterId); if (Boolean.TRUE.equals(disableRpFilter)) { buf.append(" disable_rp_filter=true"); } String msPublicKey = configurationDao.getValue("ssh.publickey"); buf.append(" authorized_key=").append(VirtualMachineGuru.getEncodedMsPublicKey(msPublicKey)); boolean externalDhcp = false; String externalDhcpStr = configurationDao.getValue("direct.attach.network.externalIpAllocator.enabled"); if (externalDhcpStr != null && externalDhcpStr.equalsIgnoreCase("true")) { externalDhcp = true; } if (Boolean.valueOf(configurationDao.getValue("system.vm.random.password"))) { buf.append(" vmpassword=").append(configurationDao.getValue("system.vm.password")); } if (StringUtils.isNotEmpty(NTPServerConfig.value())) { buf.append(" ntpserverlist=").append(NTPServerConfig.value().replaceAll("\\s+","")); } for (NicProfile nic : profile.getNics()) { int deviceId = nic.getDeviceId(); if (nic.getIPv4Address() == null) { buf.append(" eth").append(deviceId).append("ip=").append("0.0.0.0"); buf.append(" eth").append(deviceId).append("mask=").append("0.0.0.0"); } else { buf.append(" eth").append(deviceId).append("ip=").append(nic.getIPv4Address()); buf.append(" eth").append(deviceId).append("mask=").append(nic.getIPv4Netmask()); } if (nic.isDefaultNic()) { buf.append(" gateway=").append(nic.getIPv4Gateway()); } if (nic.getTrafficType() == TrafficType.Management) { String mgmt_cidr = configurationDao.getValue(Config.ManagementNetwork.key()); if (NetUtils.isValidCidrList(mgmt_cidr)) { logger.debug("Management server cidr list is " + mgmt_cidr); buf.append(" mgmtcidr=").append(mgmt_cidr); } else { logger.error("Invalid management cidr list: " + mgmt_cidr); } buf.append(" localgw=").append(dest.getPod().getGateway()); } } if (externalDhcp) { buf.append(" bootproto=dhcp"); } DataCenterVO dc = dataCenterDao.findById(profile.getVirtualMachine().getDataCenterId()); buf.append(" internaldns1=").append(dc.getInternalDns1()); if (dc.getInternalDns2() != null) { buf.append(" internaldns2=").append(dc.getInternalDns2()); } buf.append(" dns1=").append(dc.getDns1()); if (dc.getDns2() != null) { buf.append(" dns2=").append(dc.getDns2()); } if (VirtualMachine.Type.ConsoleProxy == profile.getVirtualMachine().getType()) { buf.append(" vncport=").append(getVncPort(datacenterId)); } buf.append(" keystore_password=").append(VirtualMachineGuru.getEncodedString(PasswordGenerator.generateRandomPassword(16))); String bootArgs = buf.toString(); if (logger.isDebugEnabled()) { logger.debug("Boot Args for " + profile + ": " + bootArgs); } return true; } @Override public boolean finalizeDeployment(Commands cmds, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) { finalizeCommandsOnStart(cmds, profile); ConsoleProxyVO proxy = consoleProxyDao.findById(profile.getId()); DataCenter dc = dest.getDataCenter(); List nics = profile.getNics(); for (NicProfile nic : nics) { if ((nic.getTrafficType() == TrafficType.Public && dc.getNetworkType() == NetworkType.Advanced) || (nic.getTrafficType() == TrafficType.Guest && (dc.getNetworkType() == NetworkType.Basic || dc.isSecurityGroupEnabled()))) { proxy.setPublicIpAddress(nic.getIPv4Address()); proxy.setPublicNetmask(nic.getIPv4Netmask()); proxy.setPublicMacAddress(nic.getMacAddress()); } else if (nic.getTrafficType() == TrafficType.Management) { proxy.setPrivateIpAddress(nic.getIPv4Address()); proxy.setPrivateMacAddress(nic.getMacAddress()); } } consoleProxyDao.update(proxy.getId(), proxy); return true; } @Override public boolean finalizeCommandsOnStart(Commands cmds, VirtualMachineProfile profile) { NicProfile managementNic = null; NicProfile controlNic = null; for (NicProfile nic : profile.getNics()) { if (nic.getTrafficType() == TrafficType.Management) { managementNic = nic; } else if (nic.getTrafficType() == TrafficType.Control && nic.getIPv4Address() != null) { controlNic = nic; } } if (controlNic == null) { if (managementNic == null) { logger.error("Management network doesn't exist for the console proxy vm " + profile.getVirtualMachine()); return false; } controlNic = managementNic; } if(profile.getHypervisorType() == HypervisorType.Hyperv) { controlNic = managementNic; } CheckSshCommand check = new CheckSshCommand(profile.getInstanceName(), controlNic.getIPv4Address(), 3922); cmds.addCommand("checkSsh", check); return true; } @Override public boolean finalizeStart(VirtualMachineProfile profile, long hostId, Commands cmds, ReservationContext context) { CheckSshAnswer answer = (CheckSshAnswer)cmds.getAnswer("checkSsh"); if (answer == null || !answer.getResult()) { logger.warn(String.format("Unable to use SSH on the VM [%s] due to [%s].", profile.toString(), answer == null ? "null answer" : answer.getDetails())); return false; } try { rulesManager.getSystemIpAndEnableStaticNatForVm(profile.getVirtualMachine(), false); IPAddressVO ipaddr = ipAddressDao.findByAssociatedVmId(profile.getVirtualMachine().getId()); if (ipaddr != null && ipaddr.getSystem()) { ConsoleProxyVO consoleVm = consoleProxyDao.findById(profile.getId()); consoleVm.setPublicIpAddress(ipaddr.getAddress().addr()); consoleProxyDao.update(consoleVm.getId(), consoleVm); } } catch (InsufficientAddressCapacityException ex) { logger.warn(String.format("Unable to retrieve system IP and enable static NAT for the VM [%s] due to [%s].", profile.toString(), ex.getMessage()), ex); return false; } return true; } @Override public void finalizeExpunge(VirtualMachine vm) { ConsoleProxyVO proxy = consoleProxyDao.findById(vm.getId()); proxy.setPublicIpAddress(null); proxy.setPublicMacAddress(null); proxy.setPublicNetmask(null); proxy.setPrivateMacAddress(null); proxy.setPrivateIpAddress(null); consoleProxyDao.update(proxy.getId(), proxy); } @Override public void finalizeStop(VirtualMachineProfile profile, Answer answer) { IPAddressVO ip = ipAddressDao.findByAssociatedVmId(profile.getId()); if (ip != null && ip.getSystem()) { CallContext ctx = CallContext.current(); try { rulesManager.disableStaticNat(ip.getId(), ctx.getCallingAccount(), ctx.getCallingUserId(), true); } catch (ResourceUnavailableException ex) { logger.error(String.format("Unable to disable static NAT and release system IP [%s] as a part of VM [%s] stop due to [%s].", ip.toString(), profile.toString(), ex.getMessage()), ex); } } } @Override public String getScanHandlerName() { return "consoleproxy"; } @Override public void onScanStart() { zoneHostInfoMap = getZoneHostInfo(); zoneProxyCountMap = new HashMap<>(); List listProxyCounts = consoleProxyDao.getDatacenterProxyLoadMatrix(); for (ConsoleProxyLoadInfo info : listProxyCounts) { zoneProxyCountMap.put(info.getId(), info); } zoneVmCountMap = new HashMap<>(); List listVmCounts = consoleProxyDao.getDatacenterSessionLoadMatrix(); for (ConsoleProxyLoadInfo info : listVmCounts) { zoneVmCountMap.put(info.getId(), info); } } private void scanManagementState() { ConsoleProxyManagementState state = getManagementState(); if (state != null) { switch (state) { case Auto: case Manual: case Suspending: break; case ResetSuspending: handleResetSuspending(); break; default: assert (false); } } } private void handleResetSuspending() { List runningProxies = consoleProxyDao.getProxyListInStates(State.Running); for (ConsoleProxyVO proxy : runningProxies) { logger.info("Stop console proxy {} because of we are currently in ResetSuspending management mode", proxy); stopProxy(proxy.getId()); } List proxiesInTransition = consoleProxyDao.getProxyListInStates(State.Running, State.Starting, State.Stopping); if (CollectionUtils.isEmpty(proxiesInTransition)) { logger.info("All previous console proxy VMs in transition mode ceased the mode, we will now resume to last management state"); resumeLastManagementState(); } } @Override public boolean canScan() { scanManagementState(); if (!reserveStandbyCapacity()) { if (logger.isDebugEnabled()) { logger.debug("Reserving standby capacity is disabled, skip capacity scan"); } return false; } List upPools = primaryDataStoreDao.listByStatus(StoragePoolStatus.Up); if (CollectionUtils.isEmpty(upPools)) { logger.debug("Skip capacity scan as there is no Primary Storage in 'Up' state"); return false; } return true; } @Override public Long[] getScannablePools() { List zoneIds = dataCenterDao.listEnabledNonEdgeZoneIds(); if (logger.isDebugEnabled()) { logger.debug(String.format("Enabled non-edge zones available for scan: %s", StringUtils.join(zoneIds, ","))); } return zoneIds.toArray(Long[]::new); } @Override public boolean isPoolReadyForScan(Long dataCenterId) { DataCenterVO zone = dataCenterDao.findById(dataCenterId); if (!isZoneReady(zoneHostInfoMap, zone)) { if (logger.isDebugEnabled()) { logger.debug("Zone {} is not ready to launch console proxy yet", zone); } return false; } List l = consoleProxyDao.getProxyListInStates(State.Starting, State.Stopping); if (l.size() > 0) { if (logger.isDebugEnabled()) { logger.debug("Zone {} has {} console proxy VM(s) in transition state", zone, l.size()); } return false; } if (logger.isDebugEnabled()) { logger.debug("Zone {} is ready to launch console proxy", zone); } return true; } @Override public Pair scanPool(Long dataCenterId) { ConsoleProxyLoadInfo proxyInfo = zoneProxyCountMap.get(dataCenterId); if (proxyInfo == null) { return new Pair<>(AfterScanAction.nop, null); } ConsoleProxyLoadInfo vmInfo = zoneVmCountMap.get(dataCenterId); if (vmInfo == null) { vmInfo = new ConsoleProxyLoadInfo(); } if (!checkCapacity(proxyInfo, vmInfo, dataCenterId)) { if (logger.isDebugEnabled()) { logger.debug("Expand console proxy standby capacity for zone " + proxyInfo.getName()); } return new Pair<>(AfterScanAction.expand, null); } return new Pair<>(AfterScanAction.nop, null); } @Override public void expandPool(Long dataCenterId, Object actionArgs) { allocCapacity(dataCenterId); } @Override public void shrinkPool(Long pool, Object actionArgs) { } @Override public void onScanEnd() { } @Override public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) { if (!(cmd[0] instanceof StartupProxyCommand)) { return null; } host.setType(Type.ConsoleProxy); return host; } @Override public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource, Map details, List hostTags) { return null; } @Override public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException { return null; } protected HostVO findConsoleProxyHostByName(String name) { QueryBuilder sc = QueryBuilder.create(HostVO.class); sc.and(sc.entity().getType(), Op.EQ, Type.ConsoleProxy); sc.and(sc.entity().getName(), Op.EQ, name); return sc.find(); } @Override public void prepareStop(VirtualMachineProfile profile) { } @Override public void finalizeUnmanage(VirtualMachine vm) { } public List getConsoleProxyAllocators() { return consoleProxyAllocators; } @Inject public void setConsoleProxyAllocators(List consoleProxyAllocators) { this.consoleProxyAllocators = consoleProxyAllocators; } @Override public String getConfigComponentName() { return ConsoleProxyManager.class.getSimpleName(); } @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[] { ConsoleProxySslEnabled, NoVncConsoleDefault, NoVncConsoleSourceIpCheckEnabled, ConsoleProxyServiceOffering, ConsoleProxyCapacityStandby, ConsoleProxyCapacityScanInterval, ConsoleProxyCmdPort, ConsoleProxyRestart, ConsoleProxyUrlDomain, ConsoleProxySessionMax, ConsoleProxySessionTimeout, ConsoleProxyDisableRpFilter, ConsoleProxyLaunchMax, ConsoleProxyManagementLastState, ConsoleProxyServiceManagementState }; } protected ConsoleProxyStatus parseJsonToConsoleProxyStatus(String json) throws JsonParseException { return jsonParser.fromJson(json, ConsoleProxyStatus.class); } protected void updateConsoleProxyStatus(String statusInfo, Long proxyVmId) { if (statusInfo == null) return; ConsoleProxyStatus status = null; try { status = parseJsonToConsoleProxyStatus(statusInfo); } catch (JsonParseException e) { logger.warn(String.format("Unable to parse load info [%s] from proxy {\"vmId\": %s} due to [%s].", statusInfo, proxyVmId, e.getMessage()), e); } int count = 0; byte[] details = null; if (status != null) { if (status.getConnections() != null) { count = status.getConnections().length; } if (status.getRemovedSessions() != null) { consoleAccessManager.removeSessions(status.getRemovedSessions()); } details = statusInfo.getBytes(Charset.forName("US-ASCII")); } else { logger.debug(String.format("Unable to retrieve load info from proxy {\"vmId\": %s}. Invalid load info [%s].", proxyVmId, statusInfo)); } consoleProxyDao.update(proxyVmId, count, DateUtil.currentGMTTime(), details); } private boolean isSslEnabled(Long dataCenterId) { boolean sslEnabled = ConsoleProxySslEnabled.valueIn(dataCenterId); String consoleProxyUrlDomain = ConsoleProxyUrlDomain.valueIn(dataCenterId); if( sslEnabled && (consoleProxyUrlDomain == null || consoleProxyUrlDomain.isEmpty())) { logger.warn("Empty console proxy domain, explicitly disabling SSL"); sslEnabled = false; } return sslEnabled; } private Integer getStandbyCapacity(Long datacenterId) { return Integer.parseInt(ConsoleProxyCapacityStandby.valueIn(datacenterId)); } private ServiceOfferingVO getConsoleProxyServiceOffering(Long datacenterId) throws ConfigurationException { String configKey = ConsoleProxyServiceOffering.key(); String cpvmSrvcOffIdStr = ConsoleProxyServiceOffering.valueIn(datacenterId); String warningMessage = String.format("Unable to find a service offering by the UUID or ID for console proxy VM with the value [%s] set in the configuration [%s]", cpvmSrvcOffIdStr, configKey); ServiceOfferingVO serviceOfferingVO = null; if (cpvmSrvcOffIdStr != null) { serviceOfferingVO = getServiceOfferingByUuidOrId(cpvmSrvcOffIdStr, warningMessage, configKey); } if (serviceOfferingVO == null || !serviceOfferingVO.isSystemUse()) { logger.debug("Service offering for console proxy VM is not set or not a system service offering. Creating a default service offering."); createServiceOfferingForConsoleProxy(); } return serviceOfferingVO; } private ServiceOfferingVO getServiceOfferingByUuidOrId(String cpvmSrvcOffIdStr, String warningMessage, String configKey) { ServiceOfferingVO serviceOfferingVO = serviceOfferingDao.findByUuid(cpvmSrvcOffIdStr); if (serviceOfferingVO == null) { try { logger.debug(warningMessage); serviceOfferingVO = serviceOfferingDao.findById(Long.parseLong(cpvmSrvcOffIdStr)); } catch (NumberFormatException ex) { logger.warn(String.format("Unable to find a service offering by the ID for console proxy VM with the value [%s] set in the configuration [%s]. The value is not a valid integer number. Error: [%s].", cpvmSrvcOffIdStr, configKey, ex.getMessage()), ex); } } if (serviceOfferingVO == null) { logger.warn(warningMessage); } return serviceOfferingVO; } private void createServiceOfferingForConsoleProxy() throws ConfigurationException { int ramSize = NumbersUtil.parseInt(configurationDao.getValue("console.ram.size"), DEFAULT_PROXY_VM_RAMSIZE); int cpuFreq = NumbersUtil.parseInt(configurationDao.getValue("console.cpu.mhz"), DEFAULT_PROXY_VM_CPUMHZ); List offerings = serviceOfferingDao.createSystemServiceOfferings("System Offering For Console Proxy", ServiceOffering.consoleProxyDefaultOffUniqueName, 1, ramSize, cpuFreq, 0, 0, false, null, Storage.ProvisioningType.THIN, true, null, true, VirtualMachine.Type.ConsoleProxy, true); if (offerings == null || offerings.size() < 2) { String msg = "Data integrity problem : System Offering For Console Proxy has been removed?"; logger.error(msg); throw new ConfigurationException(msg); } } }