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.util.ArrayList;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Set;
  
  import net.juniper.contrail.api.ApiConnector;
  import net.juniper.contrail.api.ObjectReference;
  import net.juniper.contrail.api.types.NetworkIpam;
  import net.juniper.contrail.api.types.Project;
  import net.juniper.contrail.api.types.SubnetType;
  import net.juniper.contrail.api.types.VirtualNetwork;
  import net.juniper.contrail.api.types.VirtualNetworkPolicyType;
  import net.juniper.contrail.api.types.VnSubnetsType;
  
  import org.apache.cloudstack.network.contrail.management.ContrailManager;
  
  import com.cloud.dc.VlanVO;
  import com.cloud.dc.dao.VlanDao;
  import com.cloud.exception.InternalErrorException;
  import com.cloud.network.Network;
  import com.cloud.network.Networks.TrafficType;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.utils.net.NetUtils;
  
  public class VirtualNetworkModel extends ModelObjectBase {
  
      private String _uuid;
      private long _id;
      private final TrafficType _trafficType;
  
      /*
       * current state for object properties
       */
      private boolean _initialized;
      private String _name;
      private String _prefix;
      private String _gateway;
      private String _projectId;
  
      /*
       * cached API server objects
       */
      private VirtualNetwork _vn;
      private NetworkIpam _ipam;
  
      private FloatingIpPoolModel _fipPoolModel;
      private NetworkPolicyModel _policyModel;
  
      public VirtualNetworkModel(Network network, String uuid, String name, TrafficType trafficType) {
          _uuid = uuid;
          _name = name;
          _trafficType = trafficType;
          if (network != null) {
              _id = network.getId();
          }
  
          if (isDynamicNetwork()) {
              assert _uuid != null : "uuid is must for dynamic networks";
          } else {
              assert _name != null : "name is must for static networks";
          }
      }
  
      /*
       * Resynchronize internal state from the cloudstack DB object.
       */
      public void build(ModelController controller, Network network) {
          setProperties(controller, network);
      }
  
      /**
       * Determine whether this network is dynamically created by cloudstack or is created by default by the contrail
       * API server.
       *
       * @return
       */
      boolean isDynamicNetwork() {
          return (_trafficType == TrafficType.Guest) || (_trafficType == TrafficType.Public);
      }
  
      @Override
      public int compareTo(ModelObject o) {
          VirtualNetworkModel other;
          try {
              other = (VirtualNetworkModel)o;
          } catch (ClassCastException ex) {
              String clsname = o.getClass().getName();
              return VirtualNetworkModel.class.getName().compareTo(clsname);
          }
  
          if (!isDynamicNetwork()) {
              if (!other.isDynamicNetwork()) {
                  // name is not unique since both management and storage networks may map to ip-fabric
                  int cmp = _name.compareTo(other.getName());
                  if (cmp != 0) {
                      return cmp;
                  }
                  return _trafficType.compareTo(other._trafficType);
              }
              return -1;
          } else if (!other.isDynamicNetwork()) {
              return 1;
          }
  
          return _uuid.compareTo(other._uuid);
      }
  
      @Override
      public void delete(ModelController controller) throws IOException {
          ApiConnector api = controller.getApiAccessor();
          for (ModelObject successor : successors()) {
              successor.delete(controller);
          }
  
          if (_policyModel != null) {
              _policyModel.removeSuccessor(this);
          }
  
          try {
              api.delete(VirtualNetwork.class, _uuid);
          } catch (IOException ex) {
              logger.warn("virtual-network delete", ex);
          }
      }
  
      @Override
      public void destroy(ModelController controller) throws IOException {
          delete(controller);
  
          for (ModelObject successor : successors()) {
              successor.destroy(controller);
          }
          clearSuccessors();
      }
  
      public String getName() {
          return _name;
      }
  
      public String getUuid() {
          return _uuid;
      }
  
      public VirtualNetwork getVirtualNetwork() {
          return _vn;
      }
  
      /**
       * Initialize the object properties based on the DB object.
       * Common code between plugin calls and DBSync.
       */
      public void setProperties(ModelController controller, Network network) {
          ContrailManager manager = controller.getManager();
          _name = manager.getCanonicalName(network);
          _prefix = network.getCidr();
          _gateway = network.getGateway();
  
          // For non-cloudstack managed network, find the uuid at this stage.
          if (!isDynamicNetwork()) {
              try {
                  _uuid = manager.findVirtualNetworkId(network);
              } catch (IOException ex) {
                  logger.warn("Unable to read virtual-network", ex);
              }
          }
  
          _id = network.getId();
  
          try {
              _projectId = manager.getProjectId(network.getDomainId(), network.getAccountId());
          } catch (IOException ex) {
              logger.warn("project read", ex);
              throw new CloudRuntimeException(ex);
          }
  
          _initialized = true;
      }
  
      @Override
      public void update(ModelController controller) throws InternalErrorException, IOException {
  
          assert _initialized;
  
          ApiConnector api = controller.getApiAccessor();
          VlanDao vlanDao = controller.getVlanDao();
          VirtualNetwork vn = _vn;
  
          if (!isDynamicNetwork()) {
              _vn = (VirtualNetwork)controller.getApiAccessor().findById(VirtualNetwork.class, _uuid);
              return;
          }
  
          assert _uuid != null : "uuid is not set";
  
          if (_vn == null) {
              vn = _vn = (VirtualNetwork)controller.getApiAccessor().findById(VirtualNetwork.class, _uuid);
              if (vn == null) {
                  vn = new VirtualNetwork();
                  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);
                      }
                      vn.setParent(project);
                  }
                  vn.setName(_name);
                  vn.setUuid(_uuid);
              }
          }
  
          if (_policyModel == null) {
              vn.clearNetworkPolicy();
          } else if (!_policyModel.hasPolicyRules()) {
              vn.clearNetworkPolicy();
              _policyModel.removeSuccessor(this);
          } else {
              vn.setNetworkPolicy(_policyModel.getPolicy(), new VirtualNetworkPolicyType(
                      new VirtualNetworkPolicyType.SequenceType(1, 0), null));
          }
  
          if (_ipam == null) {
              NetworkIpam ipam = null;
              try {
                  String ipam_id = api.findByName(NetworkIpam.class, null, "default-network-ipam");
                  if (ipam_id == null) {
                      logger.debug("could not find default-network-ipam");
                      return;
                  }
                  ipam = (NetworkIpam)api.findById(NetworkIpam.class, ipam_id);
                  if (ipam == null) {
                      logger.debug("could not find NetworkIpam with ipam_id: " + ipam_id);
                      return;
                  }
              } catch (IOException ex) {
                  logger.error(ex);
                  return;
              }
              _ipam = ipam;
          }
  
          if (_prefix != null) {
              VnSubnetsType subnet = new VnSubnetsType();
              String[] addr_pair = _prefix.split("\\/");
              subnet.addIpamSubnets(new SubnetType(addr_pair[0], Integer.parseInt(addr_pair[1])), _gateway);
              vn.setNetworkIpam(_ipam, subnet);
          } else if (_trafficType == TrafficType.Public) {
              vn.clearNetworkIpam();
              /* Subnet information for Public is stored in the vlan table */
              List<VlanVO> vlan_list = vlanDao.listVlansByNetworkId(_id);
              for (VlanVO vlan : vlan_list) {
                  String cidr = NetUtils.ipAndNetMaskToCidr(vlan.getVlanGateway(), vlan.getVlanNetmask());
                  int slash = cidr.indexOf('/');
                  String ip_addr = cidr.substring(0, slash);
                  int plen = Integer.parseInt(cidr.substring(slash + 1));
                  VnSubnetsType subnet = new VnSubnetsType();
                  subnet.addIpamSubnets(new SubnetType(ip_addr, plen), vlan.getVlanGateway());
                  vn.addNetworkIpam(_ipam, subnet);
              }
          }
  
          if (_vn == null) {
              try {
                  api.create(vn);
              } catch (Exception ex) {
                  logger.debug("virtual-network create", ex);
                  throw new CloudRuntimeException("Failed to create virtual-network", ex);
              }
              _vn = vn;
          } else {
              try {
                  api.update(vn);
              } catch (IOException ex) {
                  logger.warn("virtual-network update", ex);
                  throw new CloudRuntimeException("Unable to update virtual-network object", ex);
              }
          }
  
          for (ModelObject successor : successors()) {
              successor.update(controller);
          }
      }
  
      public void read(ModelController controller) {
          ApiConnector api = controller.getApiAccessor();
          VlanDao vlanDao = controller.getVlanDao();
          try {
              _vn = (VirtualNetwork)api.findById(VirtualNetwork.class, _uuid);
          } catch (IOException e) {
              e.printStackTrace();
          }
          if (_vn == null) {
              return;
          }
          if (_ipam == null) {
              NetworkIpam ipam = null;
              try {
                  String ipam_id = api.findByName(NetworkIpam.class, null, "default-network-ipam");
                  if (ipam_id == null) {
                      logger.debug("could not find default-network-ipam");
                      return;
                  }
                  ipam = (NetworkIpam)api.findById(NetworkIpam.class, ipam_id);
                  if (ipam == null) {
                      logger.debug("could not find NetworkIpam with ipam_id: " + ipam_id);
                      return;
                  }
              } catch (IOException ex) {
                  logger.error(ex);
                  return;
              }
              _ipam = ipam;
          }
  
          if (_prefix != null) {
              VnSubnetsType subnet = new VnSubnetsType();
              String[] addr_pair = _prefix.split("\\/");
              subnet.addIpamSubnets(new SubnetType(addr_pair[0], Integer.parseInt(addr_pair[1])), _gateway);
              _vn.setNetworkIpam(_ipam, subnet);
          } else if (_trafficType == TrafficType.Public) {
              _vn.clearNetworkIpam();
              /* Subnet information for Public is stored in the vlan table */
              List<VlanVO> vlan_list = vlanDao.listVlansByNetworkId(_id);
              for (VlanVO vlan : vlan_list) {
                  String cidr = NetUtils.ipAndNetMaskToCidr(vlan.getVlanGateway(), vlan.getVlanNetmask());
                  int slash = cidr.indexOf('/');
                  String ip_addr = cidr.substring(0, slash);
                  int plen = Integer.parseInt(cidr.substring(slash + 1));
                  VnSubnetsType subnet = new VnSubnetsType();
                  subnet.addIpamSubnets(new SubnetType(ip_addr, plen), vlan.getVlanGateway());
                  _vn.addNetworkIpam(_ipam, subnet);
              }
          }
          return;
      }
  
      @Override
      public boolean verify(ModelController controller) {
          assert _initialized : "initialized is false";
      assert _uuid != null : "uuid is not set";
  
      ApiConnector api = controller.getApiAccessor();
      VlanDao vlanDao = controller.getVlanDao();
  
      try {
          _vn = (VirtualNetwork)api.findById(VirtualNetwork.class, _uuid);
      } catch (IOException e) {
          e.printStackTrace();
      }
  
      if (_vn == null) {
          return false;
      }
  
      if (!isDynamicNetwork()) {
          return true;
      }
  
      List<String> dbSubnets = new ArrayList<String>();
      if (_trafficType == TrafficType.Public) {
          List<VlanVO> vlan_list = vlanDao.listVlansByNetworkId(_id);
          for (VlanVO vlan : vlan_list) {
              String cidr = NetUtils.ipAndNetMaskToCidr(vlan.getVlanGateway(), vlan.getVlanNetmask());
              dbSubnets.add(vlan.getVlanGateway() + cidr);
          }
      } else {
          dbSubnets.add(_gateway + _prefix);
      }
  
      List<ObjectReference<VnSubnetsType>> ipamRefs = _vn.getNetworkIpam();
      List<String> vncSubnets = new ArrayList<String>();
  
      if (ipamRefs == null && !dbSubnets.isEmpty()) {
          return false;
      }
  
      if (ipamRefs != null) {
          for (ObjectReference<VnSubnetsType> ref : ipamRefs) {
              VnSubnetsType vnSubnetType = ref.getAttr();
              if (vnSubnetType != null) {
                  List<VnSubnetsType.IpamSubnetType> subnets = vnSubnetType.getIpamSubnets();
                  if (subnets != null && !subnets.isEmpty()) {
                      VnSubnetsType.IpamSubnetType ipamSubnet = subnets.get(0);
                      vncSubnets.add(ipamSubnet.getDefaultGateway() + ipamSubnet.getSubnet().getIpPrefix() + "/" + ipamSubnet.getSubnet().getIpPrefixLen());
                  }
              }
          }
      }
      // unordered, no duplicates hence perform negation operation as set
      Set<String> diff = new HashSet<String>(dbSubnets);
      diff.removeAll(vncSubnets);
  
      if (!diff.isEmpty()) {
          logger.debug(String.format("Subnets changed, network: [id: %d, uuid: %s, name: %s]; " +
                  "db: %s, vnc: %s, diff: %s", _id, _uuid, _name, dbSubnets, vncSubnets, diff));
          return false;
      }
  
      List<ObjectReference<VirtualNetworkPolicyType>> policyRefs = _vn.getNetworkPolicy();
      if ((policyRefs == null || policyRefs.isEmpty()) && _policyModel != null) {
          return false;
      }
  
      if ((policyRefs != null && !policyRefs.isEmpty()) && _policyModel == null) {
          return false;
      }
  
      if (policyRefs != null && !policyRefs.isEmpty() && _policyModel != null) {
          ObjectReference<VirtualNetworkPolicyType> ref = policyRefs.get(0);
          if (!ref.getUuid().equals(_policyModel.getUuid())) {
              return false;
          }
      }
  
      for (ModelObject successor : successors()) {
          if (!successor.verify(controller)) {
              return false;
          }
      }
      return true;
      }
  
      @Override
      public boolean compare(ModelController controller, ModelObject o) {
          VirtualNetworkModel latest;
          assert _vn != null : "vnc virtual network current is not initialized";
  
          try {
              latest = (VirtualNetworkModel)o;
          } catch (ClassCastException ex) {
              logger.warn("Invalid model object is passed to cast to VirtualNetworkModel");
              return false;
          }
  
          try {
              latest.read(controller);
          } catch (Exception e) {
              e.printStackTrace();
              return false;
          }
          assert latest._vn != null : "vnc virtual network new is not initialized";
  
          List<ObjectReference<VnSubnetsType>> currentIpamRefs = _vn.getNetworkIpam();
          List<ObjectReference<VnSubnetsType>> newIpamRefs = latest._vn.getNetworkIpam();
          List<String> currentSubnets = new ArrayList<String>();
          List<String> newSubnets = new ArrayList<String>();
  
          if ((currentIpamRefs == null && newIpamRefs != null) || (currentIpamRefs != null && newIpamRefs == null)) {  //Check for existence only
              logger.debug("ipams differ: current=" + currentIpamRefs + ", new=" + newIpamRefs);
              return false;
          }
          if (currentIpamRefs == null) {
              return true;
          }
  
          for (ObjectReference<VnSubnetsType> ref : currentIpamRefs) {
              VnSubnetsType vnSubnetType = ref.getAttr();
              if (vnSubnetType != null) {
                  List<VnSubnetsType.IpamSubnetType> subnets = vnSubnetType.getIpamSubnets();
                  if (subnets != null && !subnets.isEmpty()) {
                      VnSubnetsType.IpamSubnetType ipamSubnet = subnets.get(0);
                      currentSubnets.add(ipamSubnet.getDefaultGateway() + ipamSubnet.getSubnet().getIpPrefix() + "/" + ipamSubnet.getSubnet().getIpPrefixLen());
                  }
              }
          }
  
          for (ObjectReference<VnSubnetsType> ref : newIpamRefs) {
              VnSubnetsType vnSubnetType = ref.getAttr();
              if (vnSubnetType != null) {
                  List<VnSubnetsType.IpamSubnetType> subnets = vnSubnetType.getIpamSubnets();
                  if (subnets != null && !subnets.isEmpty()) {
                      VnSubnetsType.IpamSubnetType ipamSubnet = subnets.get(0);
                      newSubnets.add(ipamSubnet.getDefaultGateway() + ipamSubnet.getSubnet().getIpPrefix() + "/" + ipamSubnet.getSubnet().getIpPrefixLen());
                  }
              }
          }
  
          Set<String> diff = new HashSet<String>(currentSubnets);
          diff.removeAll(newSubnets);
  
          if (!diff.isEmpty()) {
              logger.debug(String.format("Subnets differ, network: [id: %d, uuid: %s, name: %s]; db: %s, vnc: %s, diff: %s", _id, _uuid, _name, currentSubnets, newSubnets, diff));
              return false;
          }
  
          List<ObjectReference<VirtualNetworkPolicyType>> currentPolicyRefs = _vn.getNetworkPolicy();
          List<ObjectReference<VirtualNetworkPolicyType>> latestPolicyRefs = latest._vn.getNetworkPolicy();
  
          if (currentPolicyRefs == null && latestPolicyRefs == null) {
              return true;
          }
  
          if ((currentPolicyRefs == null && latestPolicyRefs != null) || (currentPolicyRefs != null
                  && latestPolicyRefs == null)) {
              return false;
          }
  
          if ((currentPolicyRefs != null && latestPolicyRefs != null) && (currentPolicyRefs.size() != latestPolicyRefs.size())) {
              return false;
          }
  
          if ((currentPolicyRefs != null && latestPolicyRefs != null) &&  currentPolicyRefs.isEmpty()
                  && latestPolicyRefs.isEmpty()) {
              return true;
          }
  
          //both must be non empty lists
          ObjectReference<VirtualNetworkPolicyType> ref1 = null;
  
          if (currentPolicyRefs != null) {
              ref1 = currentPolicyRefs.get(0);
          }
  
          ObjectReference<VirtualNetworkPolicyType> ref2 = null;
  
          if (latestPolicyRefs != null) {
              ref2 = latestPolicyRefs.get(0);
          }
  
          if (ref1 == null && ref2 == null) {
              return true;
          }
  
          if ((ref1 != null && ref2 == null) || (ref1 == null && ref2 != null)) {
              return false;
          }
  
          if ((ref1 != null && ref2 != null) && ((ref1.getUuid() != null && ref2.getUuid() == null)
                  || (ref1.getUuid() == null && ref2.getUuid() != null))) {
              return false;
          }
          if ((ref1 != null && ref2 != null) && (ref1.getUuid() == null && ref2.getUuid() == null)) {
              return true;
          }
          if ((ref1 != null && ref2 != null) && !ref1.getUuid().equals(ref2.getUuid())) {
              return false;
          }
          return true;
      }
  
      public FloatingIpPoolModel getFipPoolModel() {
          return _fipPoolModel;
      }
  
      public void setFipPoolModel(FloatingIpPoolModel fipPoolModel) {
          _fipPoolModel = fipPoolModel;
      }
  
      public NetworkPolicyModel getNetworkPolicyModel() {
          return _policyModel;
      }
  
      public void addToNetworkPolicy(NetworkPolicyModel policyModel) {
          if (_policyModel != null) {
              _policyModel.removeSuccessor(this);
          }
          _policyModel = policyModel;
          if (_policyModel != null) {
              _policyModel.addSuccessor(this);
          }
      }
  }
  