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 org.apache.cloudstack.network.contrail.model;
  
  import java.io.IOException;
  import java.text.MessageFormat;
  import java.util.List;
  import java.util.Map;
  import java.util.TreeSet;
  
  import net.juniper.contrail.api.ApiConnector;
  import net.juniper.contrail.api.types.Project;
  import net.juniper.contrail.api.types.ServiceInstance;
  import net.juniper.contrail.api.types.VirtualMachine;
  
  import org.apache.cloudstack.network.contrail.management.ContrailManager;
  import org.apache.commons.lang3.StringUtils;
  
  import com.cloud.exception.InternalErrorException;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.uservm.UserVm;
  import com.cloud.utils.UuidUtils;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.vm.NicVO;
  import com.cloud.vm.VMInstanceVO;
  import com.cloud.vm.dao.NicDao;
  import com.google.gson.Gson;
  import com.google.gson.reflect.TypeToken;
  
  public class VirtualMachineModel extends ModelObjectBase {
  
      private final String _uuid;
      private long _instanceId;
  
      /*
       * current state for object properties
       */
      private boolean _initialized;
      private boolean _active;
      private String _serviceUuid;
      private String _instanceName;
      private String _projectId;
  
      /*
       * cached API server objects
       */
      private VirtualMachine _vm;
      private ServiceInstanceModel _serviceModel;
  
      public VirtualMachineModel(VMInstanceVO vm, String uuid) {
          _uuid = uuid;
          if (vm != null) {
              _instanceId = vm.getId();
              _instanceName = vm.getInstanceName();
          }
      }
  
      /**
       * Resynchronize internal state from the cloudstack DB object.
       * @param instance
       */
      public void build(ModelController controller, VMInstanceVO instance) {
          setProperties(controller, instance);
          UserVm userVm = controller.getVmDao().findById(instance.getId());
          if (userVm != null && userVm.getUserData() != null) {
              logger.debug(String.format("vm %s user data: %s", instance, userVm.getUserData()));
              final Gson json = new Gson();
              Map<String, String> kvmap = json.fromJson(userVm.getUserData(), new TypeToken<Map<String, String>>() {
              }.getType());
              //Renamed "data" to "serviceUuid" because it's clearer.
              String serviceUuid = kvmap.get("service-instance");
              if (serviceUuid != null) {
                  /*
                   * UUID.fromString() does not validate an UUID properly. I tried, for example, informing less digits in the UUID, where 12 were expected,
                   * and the UUID.fromstring() did not thrown the exception as expected. However, if you try UUID.fromString("aaa") it breaks, but if you try
                   * UUID.fromString("3dd4fa6e-2899-4429-b818-d34fe8df5") it doesn't (the last portion should have 12, instead of 9 digits).
                   *
                   * In other fix I added the validate UUID method to the UuidUtil classes.
                   */
                  if (UuidUtils.isUuid(serviceUuid)) {
                      /* link the object with the service instance */
                      buildServiceInstance(controller, serviceUuid);
                  } else {
                      // Throw a CloudRuntimeException in case the UUID is not valid.
                      String message = "Invalid UUID ({0}) given for the service-instance for VM {1}.";
                      message = MessageFormat.format(message, serviceUuid, instance);
                      logger.warn(message);
                      throw new CloudRuntimeException(message);
                  }
              }
          }
      }
  
      /**
       * Link the virtual machine with the service instance when recovering state from database.
       *
       * @param controller
       * @param serviceUuid
       */
      private void buildServiceInstance(ModelController controller, String serviceUuid) {
          ContrailManager manager = controller.getManager();
          ApiConnector api = controller.getApiAccessor();
          _serviceUuid = serviceUuid;
  
          ServiceInstance siObj;
          try {
              siObj = (ServiceInstance) api.findById(ServiceInstance.class, serviceUuid);
          } catch (IOException ex) {
              logger.warn("service-instance read", ex);
              throw new CloudRuntimeException("Unable to read service-instance object", ex);
          }
  
          ServiceInstanceModel siModel;
          String fqn = StringUtils.join(siObj.getQualifiedName(), ':');
          siModel = manager.getDatabase().lookupServiceInstance(fqn);
          if (siModel == null) {
              siModel = new ServiceInstanceModel(serviceUuid);
              siModel.build(controller, siObj);
              manager.getDatabase().getServiceInstances().add(siModel);
          }
          /*
           * The code that was under the ELSE was never executed and due to that has been removed.
           * Also, in the case siObj was null, it was going pass it as parameter to the build() method in the
           * siModel object.
           */
          _serviceModel = siModel;
      }
  
      @Override
      public int compareTo(ModelObject o) {
          VirtualMachineModel other;
          try {
              other = (VirtualMachineModel)o;
          } catch (ClassCastException ex) {
              String clsname = o.getClass().getName();
              return VirtualMachineModel.class.getName().compareTo(clsname);
          }
          return _uuid.compareTo(other._uuid);
      }
  
      @Override
      public void delete(ModelController controller) throws IOException {
          ApiConnector api = controller.getApiAccessor();
          for (ModelObject successor : successors()) {
              successor.delete(controller);
          }
  
          try {
              api.delete(VirtualMachine.class, _uuid);
          } catch (IOException ex) {
              logger.warn("virtual-machine delete", ex);
          }
  
          if (_serviceModel != null) {
              _serviceModel.delete(controller);
          }
      }
  
      @Override
      public void destroy(ModelController controller) throws IOException {
          delete(controller);
  
          for (ModelObject successor : successors()) {
              successor.destroy(controller);
          }
  
          clearSuccessors();
  
          if (_serviceModel != null) {
              _serviceModel.removeSuccessor(this);
              _serviceModel.destroy(controller);
              ContrailManager manager = controller.getManager();
              manager.getDatabase().getServiceInstances().remove(_serviceModel);
              _serviceModel = null;
          }
      }
  
      public String getInstanceName() {
          return _instanceName;
      }
  
      public String getUuid() {
          return _uuid;
      }
  
      public VirtualMachine getVirtualMachine() {
          return _vm;
      }
  
      public VMInterfaceModel getVMInterface(String uuid) {
          TreeSet<ModelObject> tree = successors();
          VMInterfaceModel vmiKey = new VMInterfaceModel(uuid);
          VMInterfaceModel current = (VMInterfaceModel)tree.ceiling(vmiKey);
          if (current != null && current.getUuid().equals(uuid)) {
              return current;
          }
          return null;
      }
  
      public boolean isActive() {
          return _active;
      }
  
      boolean isActiveInstance(VMInstanceVO instance) {
          switch (instance.getState()) {
              case Migrating:
              case Starting:
              case Running:
              case Shutdown:
              case Stopped:
              case Stopping:
                  return true;
  
              case Destroyed:
              case Error:
              case Expunging:
                  return false;
  
              default:
                  logger.warn("Unknown VMInstance state " + instance.getState().getDescription());
          }
          return true;
      }
  
      /**
       * Initialize the object properties based on the DB object.
       * Common code between plugin calls and DBSync.
       */
      public void setProperties(ModelController controller, VMInstanceVO instance) {
          ContrailManager manager = controller.getManager();
          _instanceName = instance.getInstanceName();
          _active = isActiveInstance(instance);
  
          try {
              _projectId = manager.getProjectId(instance.getDomainId(), instance.getAccountId());
          } catch (IOException ex) {
              logger.warn("project read", ex);
              throw new CloudRuntimeException(ex);
          }
          _initialized = true;
      }
  
      /**
       * Link the virtual machine with a service instance via programmatic API call.
       * @throws IOException
       */
      public void setServiceInstance(ModelController controller, VMInstanceVO instance, ServiceInstanceModel serviceModel) throws IOException {
          _serviceUuid = serviceModel.getUuid();
          _serviceModel = serviceModel;
          serviceModel.addSuccessor(this);
          setServiceInstanceNics(controller, instance);
      }
  
      private void setServiceInstanceNics(ModelController controller, VMInstanceVO instance) throws IOException {
          NicDao nicDao = controller.getNicDao();
          ContrailManager manager = controller.getManager();
          NetworkDao networkDao = controller.getNetworkDao();
  
          List<NicVO> nics = nicDao.listByVmId(_instanceId);
          for (NicVO nic : nics) {
              String tag;
  
              switch (nic.getDeviceId()) {
                  case 0:
                      tag = "management";
                      break;
                  case 1:
                      tag = "left";
                      break;
                  case 2:
                      tag = "right";
                      break;
                  default:
                      tag = null;
              }
  
              VMInterfaceModel vmiModel = getVMInterface(nic.getUuid());
              if (vmiModel == null) {
                  vmiModel = new VMInterfaceModel(nic.getUuid());
                  vmiModel.addToVirtualMachine(this);
                  NetworkVO network = networkDao.findById(nic.getNetworkId());
                  VirtualNetworkModel vnModel = manager.getDatabase().lookupVirtualNetwork(network.getUuid(), manager.getCanonicalName(network), network.getTrafficType());
                  assert vnModel != null;
                  vmiModel.addToVirtualNetwork(vnModel);
              }
              vmiModel.setProperties(controller, instance, nic);
              vmiModel.setServiceTag(tag);
          }
      }
  
      @Override
      public void update(ModelController controller) throws InternalErrorException, IOException {
          assert _initialized;
          ApiConnector api = controller.getApiAccessor();
  
          VirtualMachine vm = _vm;
          if (vm == null) {
              _vm = vm = (VirtualMachine)api.findById(VirtualMachine.class, _uuid);
              if (vm == null) {
                  vm = new VirtualMachine();
                  if (_projectId != null) {
                      Project project;
                      try {
                          project = (Project)api.findById(Project.class, _projectId);
                      } catch (IOException ex) {
                          logger.debug("project read", ex);
                          throw new CloudRuntimeException("Failed to read project", ex);
                      }
                      vm.setParent(project);
                  }
                  vm.setName(_instanceName);
                  vm.setUuid(_uuid);
              }
          }
  
          if (_serviceModel != null) {
              vm.setServiceInstance(_serviceModel.getServiceInstance());
          }
  
          if (_vm == null) {
              try {
                  api.create(vm);
              } catch (Exception ex) {
                  logger.debug("virtual-machine create", ex);
                  throw new CloudRuntimeException("Failed to create virtual-machine", ex);
              }
              _vm = vm;
          } else {
              try {
                  api.update(vm);
              } catch (IOException ex) {
                  logger.warn("virtual-machine update", ex);
                  throw new CloudRuntimeException("Unable to update virtual-machine object", ex);
              }
          }
  
          for (ModelObject successor : successors()) {
              successor.update(controller);
          }
      }
  
      @Override
      public boolean verify(ModelController controller) {
          assert _initialized : "initialized is false";
      assert _uuid != null : "uuid is not set";
  
      ApiConnector api = controller.getApiAccessor();
  
      try {
          _vm = (VirtualMachine) api.findById(VirtualMachine.class, _uuid);
      } catch (IOException e) {
          logger.error("virtual-machine verify", e);
      }
  
      if (_vm == null) {
          return false;
      }
  
      for (ModelObject successor: successors()) {
          if (!successor.verify(controller)) {
              return false;
          }
      }
      return true;
      }
  
      @Override
      public boolean compare(ModelController controller, ModelObject current) {
          // TODO Auto-generated method stub
          return true;
      }
  }
  