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.ucs.manager;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.UUID;
  import java.util.concurrent.Executors;
  import java.util.concurrent.ScheduledExecutorService;
  import java.util.concurrent.TimeUnit;
  
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  
  import org.apache.cloudstack.api.AddUcsManagerCmd;
  import org.apache.cloudstack.api.AssociateUcsProfileToBladeCmd;
  import org.apache.cloudstack.api.DeleteUcsManagerCmd;
  import org.apache.cloudstack.api.ListUcsBladeCmd;
  import org.apache.cloudstack.api.ListUcsManagerCmd;
  import org.apache.cloudstack.api.ListUcsProfileCmd;
  import org.apache.cloudstack.api.response.ListResponse;
  import org.apache.cloudstack.api.response.UcsBladeResponse;
  import org.apache.cloudstack.api.response.UcsManagerResponse;
  import org.apache.cloudstack.api.response.UcsProfileResponse;
  import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
  import org.apache.cloudstack.managed.context.ManagedContextRunnable;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  
  import com.cloud.configuration.Config;
  import com.cloud.dc.ClusterDetailsDao;
  import com.cloud.dc.DataCenterVO;
  import com.cloud.dc.dao.ClusterDao;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.resource.ResourceService;
  import com.cloud.ucs.database.UcsBladeDao;
  import com.cloud.ucs.database.UcsBladeVO;
  import com.cloud.ucs.database.UcsManagerDao;
  import com.cloud.ucs.database.UcsManagerVO;
  import com.cloud.ucs.structure.ComputeBlade;
  import com.cloud.ucs.structure.UcsCookie;
  import com.cloud.ucs.structure.UcsProfile;
  import com.cloud.utils.concurrency.NamedThreadFactory;
  import com.cloud.utils.db.DB;
  import com.cloud.utils.db.QueryBuilder;
  import com.cloud.utils.db.SearchCriteria.Op;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.utils.xmlobject.XmlObject;
  import com.cloud.utils.xmlobject.XmlObjectParser;
  
  public class UcsManagerImpl implements UcsManager {
      protected Logger logger = LogManager.getLogger(getClass());
      public static final Long COOKIE_TTL = TimeUnit.MILLISECONDS.convert(100L, TimeUnit.MINUTES);
      public static final Long COOKIE_REFRESH_TTL = TimeUnit.MILLISECONDS.convert(10L, TimeUnit.MINUTES);
  
      @Inject
      private UcsManagerDao ucsDao;
      @Inject
      private ResourceService resourceService;
      @Inject
      private ClusterDao clusterDao;
      @Inject
      private ClusterDetailsDao clusterDetailsDao;
      @Inject
      private UcsBladeDao bladeDao;
      @Inject
      private HostDao hostDao;
      @Inject
      private DataCenterDao dcDao;
      @Inject
      private ConfigurationDao configDao;
  
      private final Map<Long, UcsCookie> cookies = new HashMap<Long, UcsCookie>();
      private String name;
      private int runLevel;
      private Map<String, Object> params;
      private ScheduledExecutorService syncBladesExecutor;
      private int syncBladeInterval;
  
      private class SyncBladesThread extends ManagedContextRunnable {
  
          private void discoverNewBlades(Map<String, UcsBladeVO> previous, Map<String, ComputeBlade> now, UcsManagerVO mgr) {
              for (Map.Entry<String, ComputeBlade> e : now.entrySet()) {
                  String dn = e.getKey();
                  if (previous.keySet().contains(dn)) {
                      continue;
                  }
  
                  ComputeBlade nc = e.getValue();
                  UcsBladeVO vo = new UcsBladeVO();
                  vo.setDn(nc.getDn());
                  vo.setUcsManagerId(mgr.getId());
                  vo.setUuid(UUID.randomUUID().toString());
                  bladeDao.persist(vo);
                  logger.debug(String.format("discovered a new UCS blade[dn:%s] during sync", nc.getDn()));
              }
          }
  
          private void decommissionFadedBlade(Map<String, UcsBladeVO> previous, Map<String, ComputeBlade> now) {
              for (Map.Entry<String, UcsBladeVO> e : previous.entrySet()) {
                  String dn = e.getKey();
                  if (now.keySet().contains(dn)) {
                      continue;
                  }
  
                  UcsBladeVO vo = e.getValue();
                  bladeDao.remove(vo.getId());
                  logger.debug(String.format("decommission faded blade[dn:%s] during sync", vo.getDn()));
              }
          }
  
          private void syncBlades(UcsManagerVO mgr) {
              QueryBuilder<UcsBladeVO> q = QueryBuilder.create(UcsBladeVO.class);
              q.and(q.entity().getUcsManagerId(), Op.EQ, mgr.getId());
              List<UcsBladeVO> pblades = q.list();
              if (pblades.isEmpty()) {
                  return;
              }
  
              Map<String, UcsBladeVO> previousBlades = new HashMap<String, UcsBladeVO>(pblades.size());
              for (UcsBladeVO b : pblades) {
                  previousBlades.put(b.getDn(), b);
              }
  
              List<ComputeBlade> cblades = listBlades(mgr.getId());
              Map<String, ComputeBlade> currentBlades = new HashMap<String, ComputeBlade>(cblades.size());
              for (ComputeBlade c : cblades) {
                  currentBlades.put(c.getDn(), c);
              }
  
              discoverNewBlades(previousBlades, currentBlades, mgr);
              decommissionFadedBlade(previousBlades, currentBlades);
          }
  
          @Override
          protected void runInContext() {
              try {
                  List<UcsManagerVO> mgrs = ucsDao.listAll();
                  for (UcsManagerVO mgr : mgrs) {
                      syncBlades(mgr);
                  }
              } catch (Throwable t) {
                  logger.warn(t.getMessage(), t);
              }
          }
  
      }
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          return true;
      }
  
      @Override
      public boolean start() {
          try {
              syncBladeInterval = Integer.parseInt(configDao.getValue(Config.UCSSyncBladeInterval.key()));
          } catch (NumberFormatException e) {
              syncBladeInterval = 600;
          }
          syncBladesExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("UCS-SyncBlades"));
          syncBladesExecutor.scheduleAtFixedRate(new SyncBladesThread(), syncBladeInterval, syncBladeInterval, TimeUnit.SECONDS);
          return true;
      }
  
      @Override
      public boolean stop() {
          return true;
      }
  
      @Override
      public String getName() {
          return name;
      }
  
      private void discoverBlades(UcsManagerVO ucsMgrVo) {
          List<ComputeBlade> blades = listBlades(ucsMgrVo.getId());
          for (ComputeBlade b : blades) {
              UcsBladeVO vo = new UcsBladeVO();
              vo.setDn(b.getDn());
              vo.setUcsManagerId(ucsMgrVo.getId());
              vo.setUuid(UUID.randomUUID().toString());
              bladeDao.persist(vo);
          }
      }
  
      @Override
      @DB
      public UcsManagerResponse addUcsManager(AddUcsManagerCmd cmd) {
          QueryBuilder<UcsManagerVO> q = QueryBuilder.create(UcsManagerVO.class);
          q.and(q.entity().getUrl(), Op.EQ, cmd.getUrl());
          UcsManagerVO mgrvo = q.find();
          if (mgrvo != null) {
              throw new IllegalArgumentException(String.format("duplicate UCS manager. url[%s] is used by another UCS manager already", cmd.getUrl()));
          }
  
          try {
              UcsManagerVO vo = new UcsManagerVO();
              vo.setUuid(UUID.randomUUID().toString());
              vo.setPassword(cmd.getPassword());
              vo.setUrl(cmd.getUrl());
              vo.setUsername(cmd.getUsername());
              vo.setZoneId(cmd.getZoneId());
              vo.setName(cmd.getName());
  
              mgrvo = ucsDao.persist(vo);
              UcsManagerResponse rsp = new UcsManagerResponse();
              rsp.setId(String.valueOf(vo.getId()));
              rsp.setName(vo.getName());
              rsp.setUrl(vo.getUrl());
              rsp.setZoneId(String.valueOf(vo.getZoneId()));
  
              discoverBlades(vo);
              return rsp;
          } catch (CloudRuntimeException e) {
              if (mgrvo != null) {
                  ucsDao.remove(mgrvo.getId());
              }
              throw e;
          }
      }
  
      private String getCookie(Long ucsMgrId) {
          try {
              UcsCookie ucsCookie = cookies.get(ucsMgrId);
              long currentTime = System.currentTimeMillis();
              UcsManagerVO mgrvo = ucsDao.findById(ucsMgrId);
              UcsHttpClient client = new UcsHttpClient(mgrvo.getUrl());
              String cmd = null;
              if (ucsCookie == null) {
                  cmd = UcsCommands.loginCmd(mgrvo.getUsername(), mgrvo.getPassword());
              } else {
                  String cookie = ucsCookie.getCookie();
                  long cookieStartTime = ucsCookie.getStartTime();
                  if (currentTime - cookieStartTime > COOKIE_TTL) {
                      cmd = UcsCommands.loginCmd(mgrvo.getUsername(), mgrvo.getPassword());
                  } else if (currentTime - cookieStartTime > COOKIE_REFRESH_TTL) {
                      cmd = UcsCommands.refreshCmd(mgrvo.getUsername(), mgrvo.getPassword(), cookie);
                  }
              }
              if (!(cmd == null)) {
                  String ret = client.call(cmd);
                  XmlObject xo = XmlObjectParser.parseFromString(ret);
                  String cookie = xo.get("outCookie");
                  ucsCookie = new UcsCookie(cookie, currentTime);
                  cookies.put(ucsMgrId, ucsCookie);
                  //cookiesTime.put(cookie, currentTime); //This is currentTime on purpose, and not latest time.
              }
              return ucsCookie.getCookie();
          } catch (Exception e) {
              throw new CloudRuntimeException("Cannot get cookie", e);
          }
      }
  
      private List<ComputeBlade> listBlades(Long ucsMgrId) {
          String cookie = getCookie(ucsMgrId);
          UcsManagerVO mgrvo = ucsDao.findById(ucsMgrId);
          UcsHttpClient client = new UcsHttpClient(mgrvo.getUrl());
          String cmd = UcsCommands.listComputeBlades(cookie);
          String ret = client.call(cmd);
          return ComputeBlade.fromXmString(ret);
      }
  
      private List<UcsProfile> getUcsProfiles(Long ucsMgrId) {
          String cookie = getCookie(ucsMgrId);
          UcsManagerVO mgrvo = ucsDao.findById(ucsMgrId);
          String cmd = UcsCommands.listProfiles(cookie);
          UcsHttpClient client = new UcsHttpClient(mgrvo.getUrl());
          String res = client.call(cmd);
          List<UcsProfile> profiles = UcsProfile.fromXmlString(res);
          return profiles;
      }
  
      @Override
      public ListResponse<UcsProfileResponse> listUcsProfiles(ListUcsProfileCmd cmd) {
          List<UcsProfile> profiles = getUcsProfiles(cmd.getUcsManagerId());
          ListResponse<UcsProfileResponse> response = new ListResponse<UcsProfileResponse>();
          List<UcsProfileResponse> rs = new ArrayList<UcsProfileResponse>();
          for (UcsProfile p : profiles) {
              UcsProfileResponse r = new UcsProfileResponse();
              r.setObjectName("ucsprofile");
              r.setDn(p.getDn());
              rs.add(r);
          }
          response.setResponses(rs);
          return response;
      }
  
      private String cloneProfile(Long ucsMgrId, String srcDn, String newProfileName) {
          UcsManagerVO mgrvo = ucsDao.findById(ucsMgrId);
          UcsHttpClient client = new UcsHttpClient(mgrvo.getUrl());
          String cookie = getCookie(ucsMgrId);
          String cmd = UcsCommands.cloneProfile(cookie, srcDn, newProfileName);
          String res = client.call(cmd);
          XmlObject xo = XmlObjectParser.parseFromString(res);
          return xo.get("outConfig.lsServer.dn");
      }
  
      private boolean isProfileAssociated(Long ucsMgrId, String dn) {
          UcsManagerVO mgrvo = ucsDao.findById(ucsMgrId);
          UcsHttpClient client = new UcsHttpClient(mgrvo.getUrl());
          String cookie = getCookie(ucsMgrId);
          String cmd = UcsCommands.configResolveDn(cookie, dn);
          String res = client.call(cmd);
          XmlObject xo = XmlObjectParser.parseFromString(res);
          logger.debug(String.format("association response is %s", res));
  
          if (xo.get("outConfig.computeBlade.association").equals("none")) {
              throw new CloudRuntimeException(String.format("cannot associated a profile to blade[dn:%s]. please check your UCS manasger for detailed error information",
                  dn));
          }
  
          return xo.get("outConfig.computeBlade.association").equals("associated");
      }
  
      @Override
      public UcsBladeResponse associateProfileToBlade(AssociateUcsProfileToBladeCmd cmd) {
          QueryBuilder<UcsBladeVO> q = QueryBuilder.create(UcsBladeVO.class);
          q.and(q.entity().getUcsManagerId(), Op.EQ, cmd.getUcsManagerId());
          q.and(q.entity().getId(), Op.EQ, cmd.getBladeId());
          UcsBladeVO bvo = q.find();
          if (bvo == null) {
              throw new IllegalArgumentException(String.format("cannot find UCS blade[id:%s, ucs manager id:%s]", cmd.getBladeId(), cmd.getUcsManagerId()));
          }
  
          if (bvo.getHostId() != null) {
              throw new CloudRuntimeException(String.format("blade[id:%s,  dn:%s] has been associated with host[id:%s]", bvo.getId(), bvo.getDn(), bvo.getHostId()));
          }
  
          UcsManagerVO mgrvo = ucsDao.findById(cmd.getUcsManagerId());
          String cookie = getCookie(cmd.getUcsManagerId());
          String pdn = cloneProfile(mgrvo.getId(), cmd.getProfileDn(), "profile-for-blade-" + bvo.getId());
          String ucscmd = UcsCommands.associateProfileToBlade(cookie, pdn, bvo.getDn());
          UcsHttpClient client = new UcsHttpClient(mgrvo.getUrl());
          String res = client.call(ucscmd);
          int count = 0;
          int timeout = 600;
          while (count < timeout) {
              if (isProfileAssociated(mgrvo.getId(), bvo.getDn())) {
                  break;
              }
  
              try {
                  TimeUnit.SECONDS.sleep(2);
              } catch (InterruptedException e) {
                  throw new CloudRuntimeException(e);
              }
  
              count += 2;
          }
  
          if (count >= timeout) {
              throw new CloudRuntimeException(String.format("associating profile[%s] to balde[%s] timeout after 600 seconds", pdn, bvo.getDn()));
          }
  
          bvo.setProfileDn(pdn);
          bladeDao.update(bvo.getId(), bvo);
  
          UcsBladeResponse rsp = bladeVOToResponse(bvo);
  
          logger.debug(String.format("successfully associated profile[%s] to blade[%s]", pdn, bvo.getDn()));
          return rsp;
      }
  
      private String hostIdToUuid(Long hostId) {
          if (hostId == null) {
              return null;
          }
          HostVO vo = hostDao.findById(hostId);
          return vo.getUuid();
      }
  
      private String zoneIdToUuid(Long zoneId) {
          DataCenterVO vo = dcDao.findById(zoneId);
          return vo.getUuid();
      }
  
      private String ucsManagerIdToUuid(Long ucsMgrId) {
          UcsManagerVO vo = ucsDao.findById(ucsMgrId);
          return vo.getUuid();
      }
  
      @Override
      public ListResponse<UcsManagerResponse> listUcsManager(ListUcsManagerCmd cmd) {
          List<UcsManagerResponse> rsps = new ArrayList<UcsManagerResponse>();
          ListResponse<UcsManagerResponse> response = new ListResponse<UcsManagerResponse>();
          if (cmd.getId() != null) {
              UcsManagerVO vo = ucsDao.findById(cmd.getId());
              UcsManagerResponse rsp = new UcsManagerResponse();
              rsp.setObjectName("ucsmanager");
              rsp.setId(vo.getUuid());
              rsp.setName(vo.getName());
              rsp.setUrl(vo.getUrl());
              rsp.setZoneId(zoneIdToUuid(vo.getZoneId()));
              rsps.add(rsp);
              response.setResponses(rsps);
              return response;
          }
  
          QueryBuilder<UcsManagerVO> serv = QueryBuilder.create(UcsManagerVO.class);
          serv.and(serv.entity().getZoneId(), Op.EQ, cmd.getZoneId());
          List<UcsManagerVO> vos = serv.list();
  
          for (UcsManagerVO vo : vos) {
              UcsManagerResponse rsp = new UcsManagerResponse();
              rsp.setObjectName("ucsmanager");
              rsp.setId(vo.getUuid());
              rsp.setName(vo.getName());
              rsp.setUrl(vo.getUrl());
              rsp.setZoneId(zoneIdToUuid(vo.getZoneId()));
              rsps.add(rsp);
          }
          response.setResponses(rsps);
          return response;
      }
  
      private UcsBladeResponse bladeVOToResponse(UcsBladeVO vo) {
          UcsBladeResponse rsp = new UcsBladeResponse();
          rsp.setObjectName("ucsblade");
          rsp.setId(vo.getUuid());
          rsp.setDn(vo.getDn());
          rsp.setHostId(hostIdToUuid(vo.getHostId()));
          rsp.setAssociatedProfileDn(vo.getProfileDn());
          rsp.setUcsManagerId(ucsManagerIdToUuid(vo.getUcsManagerId()));
          return rsp;
      }
  
      @Override
      public ListResponse<UcsBladeResponse> listUcsBlades(ListUcsBladeCmd cmd) {
          QueryBuilder<UcsBladeVO> serv = QueryBuilder.create(UcsBladeVO.class);
          serv.and(serv.entity().getUcsManagerId(), Op.EQ, cmd.getUcsManagerId());
          List<UcsBladeVO> vos = serv.list();
  
          List<UcsBladeResponse> rsps = new ArrayList<UcsBladeResponse>(vos.size());
          for (UcsBladeVO vo : vos) {
              UcsBladeResponse rsp = bladeVOToResponse(vo);
              rsps.add(rsp);
          }
  
          ListResponse<UcsBladeResponse> response = new ListResponse<UcsBladeResponse>();
          response.setResponses(rsps);
  
          return response;
      }
  
      @Override
      public void setName(String name) {
          this.name = 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 List<Class<?>> getCommands() {
          List<Class<?>> cmds = new ArrayList<Class<?>>();
          cmds.add(ListUcsBladeCmd.class);
          cmds.add(ListUcsManagerCmd.class);
          cmds.add(ListUcsProfileCmd.class);
          cmds.add(AddUcsManagerCmd.class);
          cmds.add(AssociateUcsProfileToBladeCmd.class);
          cmds.add(DeleteUcsManagerCmd.class);
          return cmds;
      }
  
      @Override
      public void deleteUcsManager(Long id) {
          QueryBuilder<UcsBladeVO> serv = QueryBuilder.create(UcsBladeVO.class);
          serv.and(serv.entity().getUcsManagerId(), Op.EQ, id);
          List<UcsBladeVO> vos = serv.list();
          for (UcsBladeVO vo : vos) {
              bladeDao.remove(vo.getId());
          }
          ucsDao.remove(id);
      }
  }
  