content: // 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.hyperv.manager;
  
  import java.io.File;
  import java.io.IOException;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.net.URL;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Random;
  
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  
  import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
  import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
  import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
  import org.apache.cloudstack.utils.identity.ManagementServerNode;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  
  import com.cloud.configuration.Config;
  import com.cloud.storage.JavaStorageLayer;
  import com.cloud.storage.StorageLayer;
  import com.cloud.utils.FileUtil;
  import com.cloud.utils.NumbersUtil;
  import com.cloud.utils.db.GlobalLock;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.utils.script.Script;
  import com.cloud.vm.dao.NicDao;
  import com.cloud.vm.dao.VMInstanceDao;
  
  public class HypervManagerImpl implements HypervManager {
      protected Logger logger = LogManager.getLogger(getClass());
  
      private String name;
      private int runLevel;
      private Map<String, Object> params;
  
      private int _timeout;
      Random _rand = new Random(System.currentTimeMillis());
  
      Map<String, String> _storageMounts = new HashMap<String, String>();
      StorageLayer _storage;
  
      @Inject ConfigurationDao _configDao;
      @Inject DataStoreManager _dataStoreMgr;
      @Inject VMInstanceDao _vminstanceDao;
      @Inject NicDao _nicDao;
      int _routerExtraPublicNics = 2;
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          if (params != null) {
              String value = (String)params.get("scripts.timeout");
              _timeout = NumbersUtil.parseInt(value, 30) * 1000;
              _storage = (StorageLayer)params.get(StorageLayer.InstanceConfigKey);
          }
  
          if (_storage == null) {
              _storage = new JavaStorageLayer();
              _storage.configure("StorageLayer", params);
          }
          _routerExtraPublicNics = NumbersUtil.parseInt(_configDao.getValue(Config.RouterExtraPublicNics.key()), 2);
          return true;
      }
  
      @Override
      public boolean start() {
          startupCleanup(getMountParent());
          return true;
      }
  
      @Override
      public boolean stop() {
          shutdownCleanup();
          return true;
      }
  
      @Override
      public void setName(String name) {
          this.name = name;
      }
  
      @Override
      public String getName() {
          return name;
      }
  
      @Override
      public void setConfigParams(Map<String, Object> params) {
          this.params = params;
      }
  
      @Override
      public Map<String, Object> getConfigParams() {
          return params;
      }
  
      @Override
      public int getRunLevel() {
          return runLevel;
      }
  
      @Override
      public void setRunLevel(int level) {
          runLevel = level;
      }
  
      @Override
      public String prepareSecondaryStorageStore(long zoneId) {
          String secondaryStorageUri = getSecondaryStorageStoreUrl(zoneId);
          if (secondaryStorageUri == null) {
              logger.debug("Secondary storage uri for dc " + zoneId + " couldn't be obtained");
          } else {
              prepareSecondaryStorageStore(secondaryStorageUri);
          }
  
          return secondaryStorageUri;
      }
  
      private String getSecondaryStorageStoreUrl(long zoneId) {
          String secUrl = null;
          DataStore secStore = _dataStoreMgr.getImageStoreWithFreeCapacity(zoneId);
          if (secStore != null) {
              secUrl = secStore.getUri();
          }
  
          if (secUrl == null) {
              logger.warn("Secondary storage uri couldn't be retrieved");
          }
  
          return secUrl;
      }
  
      private void prepareSecondaryStorageStore(String storageUrl) {
          String mountPoint = getMountPoint(storageUrl);
  
          GlobalLock lock = GlobalLock.getInternLock("prepare.systemvm");
          try {
              if (lock.lock(3600)) {
                  try {
                      File patchFolder = new File(mountPoint + "/systemvm");
                      if (!patchFolder.exists()) {
                          if (!patchFolder.mkdirs()) {
                              String msg = "Unable to create systemvm folder on secondary storage. location: " + patchFolder.toString();
                              logger.error(msg);
                              throw new CloudRuntimeException(msg);
                          }
                      }
  
                      File srcIso = getSystemVMPatchIsoFile();
                      File destIso = new File(mountPoint + "/systemvm/" + getSystemVMIsoFileNameOnDatastore());
                      if (!destIso.exists()) {
                          logger.info("Copy System VM patch ISO file to secondary storage. source ISO: " +
                              srcIso.getAbsolutePath() + ", destination: " + destIso.getAbsolutePath());
                          try {
                              FileUtil.copyfile(srcIso, destIso);
                          } catch (IOException e) {
                              logger.error("Unexpected exception ", e);
  
                              String msg = "Unable to copy systemvm ISO on secondary storage. src location: " + srcIso.toString() + ", dest location: " + destIso;
                              logger.error(msg);
                              throw new CloudRuntimeException(msg);
                          }
                      } else {
                          if (logger.isTraceEnabled()) {
                              logger.trace("SystemVM ISO file " + destIso.getPath() + " already exists");
                          }
                      }
                  } finally {
                      lock.unlock();
                  }
              }
          } finally {
              lock.releaseRef();
          }
      }
  
      private String getMountPoint(String storageUrl) {
          String mountPoint = null;
          synchronized (_storageMounts) {
              mountPoint = _storageMounts.get(storageUrl);
              if (mountPoint != null) {
                  return mountPoint;
              }
  
              URI uri;
              try {
                  uri = new URI(storageUrl);
              } catch (URISyntaxException e) {
                  logger.error("Invalid storage URL format ", e);
                  throw new CloudRuntimeException("Unable to create mount point due to invalid storage URL format " + storageUrl);
              }
  
              mountPoint = mount(File.separator + File.separator + uri.getHost() + uri.getPath(), getMountParent(),
                  uri.getScheme(), uri.getQuery());
              if (mountPoint == null) {
                  logger.error("Unable to create mount point for " + storageUrl);
                  return "/mnt/sec";
              }
  
              _storageMounts.put(storageUrl, mountPoint);
              return mountPoint;
          }
      }
  
      protected String mount(String path, String parent, String scheme, String query) {
          String mountPoint = setupMountPoint(parent);
          if (mountPoint == null) {
              logger.warn("Unable to create a mount point");
              return null;
          }
  
          Script script = null;
          String result = null;
          if (scheme.equals("cifs")) {
              String user = System.getProperty("user.name");
              Script command = new Script(true, "mount", _timeout, logger);
              command.add("-t", "cifs");
              command.add(path);
              command.add(mountPoint);
  
              if (user != null) {
                  command.add("-o", "uid=" + user + ",gid=" + user);
              }
  
              if (query != null) {
                  query = query.replace('&', ',');
                  command.add("-o", query);
              }
  
              result = command.execute();
          }
  
          if (result != null) {
              logger.warn("Unable to mount " + path + " due to " + result);
              File file = new File(mountPoint);
              if (file.exists()) {
                  file.delete();
              }
              return null;
          }
  
          // Change permissions for the mountpoint
          script = new Script(true, "chmod", _timeout, logger);
          script.add("-R", "777", mountPoint);
          result = script.execute();
          if (result != null) {
              logger.warn("Unable to set permissions for " + mountPoint + " due to " + result);
          }
          return mountPoint;
      }
  
      private String setupMountPoint(String parent) {
          String mountPoint = null;
          long mshostId = ManagementServerNode.getManagementServerId();
          for (int i = 0; i < 10; i++) {
              String mntPt = parent + File.separator + String.valueOf(mshostId) + "." + Integer.toHexString(_rand.nextInt(Integer.MAX_VALUE));
              File file = new File(mntPt);
              if (!file.exists()) {
                  if (_storage.mkdir(mntPt)) {
                      mountPoint = mntPt;
                      break;
                  }
              }
              logger.error("Unable to create mount: " + mntPt);
          }
  
          return mountPoint;
      }
  
      private String getSystemVMIsoFileNameOnDatastore() {
          String version = this.getClass().getPackage().getImplementationVersion();
          String fileName = "systemvm-" + version + ".iso";
          return fileName.replace(':', '-');
      }
  
      private File getSystemVMPatchIsoFile() {
          // locate systemvm.iso
          URL url = this.getClass().getClassLoader().getResource("vms/systemvm.iso");
          File isoFile = null;
          if (url != null) {
              isoFile = new File(url.getPath());
          }
  
          if (isoFile == null || !isoFile.exists()) {
              isoFile = new File("/usr/share/cloudstack-common/vms/systemvm.iso");
          }
  
          assert (isoFile != null);
          if (!isoFile.exists()) {
              logger.error("Unable to locate systemvm.iso in your setup at " + isoFile.toString());
          }
          return isoFile;
      }
  
      private String getMountParent() {
          String mountParent = _configDao.getValue(Config.MountParent.key());
          if (mountParent == null) {
              mountParent = File.separator + "mnt";
          }
  
          String instance = _configDao.getValue(Config.InstanceName.key());
          if (instance == null) {
              instance = "DEFAULT";
          }
  
          if (instance != null) {
              mountParent = mountParent + File.separator + instance;
          }
  
          return mountParent;
      }
  
      private void startupCleanup(String parent) {
          logger.info("Cleanup mounted mount points used in previous session");
  
          long mshostId = ManagementServerNode.getManagementServerId();
  
          // cleanup left-over NFS mounts from previous session
          String[] mounts = _storage.listFiles(parent + File.separator + String.valueOf(mshostId) + ".*");
          if (mounts != null && mounts.length > 0) {
              for (String mountPoint : mounts) {
                  logger.info("umount NFS mount from previous session: " + mountPoint);
  
                  String result = null;
                  Script command = new Script(true, "umount", _timeout, logger);
                  command.add(mountPoint);
                  result = command.execute();
                  if (result != null) {
                      logger.warn("Unable to umount " + mountPoint + " due to " + result);
                  }
                  File file = new File(mountPoint);
                  if (file.exists()) {
                      file.delete();
                  }
              }
          }
      }
  
      private void shutdownCleanup() {
          logger.info("Cleanup mounted mount points used in current session");
          synchronized (_storageMounts) {
               for (String mountPoint : _storageMounts.values()) {
                  logger.info("umount NFS mount: " + mountPoint);
  
                  String result = null;
                  Script command = new Script(true, "umount", _timeout, logger);
                  command.add(mountPoint);
                  result = command.execute();
                  if (result != null) {
                      logger.warn("Unable to umount " + mountPoint + " due to " + result);
                  }
                  File file = new File(mountPoint);
                  if (file.exists()) {
                      file.delete();
                  }
              }
          }
      }
  
      @Override
      public int getRouterExtraPublicNics() {
          return _routerExtraPublicNics;
      }
  }
  