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.network.resource;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  
  import javax.naming.ConfigurationException;
  
  
  import com.cloud.agent.IAgentControl;
  import com.cloud.agent.api.Answer;
  import com.cloud.agent.api.UpdateBcfRouterCommand;
  import com.cloud.agent.api.BcfAnswer;
  import com.cloud.agent.api.CacheBcfTopologyCommand;
  import com.cloud.agent.api.Command;
  import com.cloud.agent.api.CreateBcfRouterCommand;
  import com.cloud.agent.api.CreateBcfRouterInterfaceCommand;
  import com.cloud.agent.api.CreateBcfSegmentCommand;
  import com.cloud.agent.api.CreateBcfAttachmentCommand;
  import com.cloud.agent.api.CreateBcfStaticNatCommand;
  import com.cloud.agent.api.DeleteBcfSegmentCommand;
  import com.cloud.agent.api.DeleteBcfAttachmentCommand;
  import com.cloud.agent.api.DeleteBcfStaticNatCommand;
  import com.cloud.agent.api.GetControllerDataAnswer;
  import com.cloud.agent.api.GetControllerDataCommand;
  import com.cloud.agent.api.MaintainAnswer;
  import com.cloud.agent.api.MaintainCommand;
  import com.cloud.agent.api.PingCommand;
  import com.cloud.agent.api.ReadyAnswer;
  import com.cloud.agent.api.ReadyCommand;
  import com.cloud.agent.api.StartupBigSwitchBcfCommand;
  import com.cloud.agent.api.StartupCommand;
  import com.cloud.agent.api.SyncBcfTopologyCommand;
  import com.cloud.agent.api.UpdateBcfAttachmentCommand;
  import com.cloud.host.Host;
  import com.cloud.host.Host.Type;
  import com.cloud.network.bigswitch.AclData;
  import com.cloud.network.bigswitch.BigSwitchBcfApi;
  import com.cloud.network.bigswitch.BigSwitchBcfApiException;
  import com.cloud.network.bigswitch.Capabilities;
  import com.cloud.network.bigswitch.ControlClusterStatus;
  import com.cloud.network.bigswitch.ControllerData;
  import com.cloud.network.bigswitch.FloatingIpData;
  import com.cloud.network.bigswitch.NetworkData;
  import com.cloud.network.bigswitch.AttachmentData;
  import com.cloud.network.bigswitch.RouterData;
  import com.cloud.network.bigswitch.RouterInterfaceData;
  import com.cloud.network.bigswitch.TopologyData;
  import com.cloud.resource.ServerResource;
  import com.cloud.utils.component.ManagerBase;
  
  public class BigSwitchBcfResource extends ManagerBase implements ServerResource {
  
      private String _name;
      private String _guid;
      private String _zoneId;
      private int _numRetries;
  
      private BigSwitchBcfApi _bigswitchBcfApi;
      private TopologyData _latestTopology = null;
      private boolean initTopologySyncDone = false;
  
      protected BigSwitchBcfApi createBigSwitchBcfApi() {
          return new BigSwitchBcfApi();
      }
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
  
          _name = (String)params.get("name");
          if (_name == null) {
              throw new ConfigurationException("Unable to find name");
          }
  
          _guid = (String)params.get("guid");
          if (_guid == null) {
              throw new ConfigurationException("Unable to find the guid");
          }
  
          _zoneId = (String)params.get("zoneId");
          if (_zoneId == null) {
              throw new ConfigurationException("Unable to find zone");
          }
  
          _numRetries = 2;
  
          String hostname = (String)params.get("hostname");
          if (hostname == null) {
              throw new ConfigurationException("Missing host name from params: " + params);
          }
  
          String username = (String) params.get("username");
          if (username == null) {
              throw new ConfigurationException("Missing user name from params: " + params);
          }
  
          String password = (String) params.get("password");
          if (password == null) {
              throw new ConfigurationException("Missing password from params: " + params);
          }
  
          Boolean nat = Boolean.parseBoolean((String) params.get("nat"));
          if (nat == null) {
              throw new ConfigurationException("Missing password from params: " + params);
          }
  
          _bigswitchBcfApi = createBigSwitchBcfApi();
          _bigswitchBcfApi.setControllerAddress(hostname);
          _bigswitchBcfApi.setControllerUsername(username);
          _bigswitchBcfApi.setControllerPassword(password);
          _bigswitchBcfApi.setControllerNat(nat);
          _bigswitchBcfApi.setZoneId(_zoneId);
  
          return true;
      }
  
      @Override
      public boolean start() {
          return true;
      }
  
      @Override
      public boolean stop() {
          return true;
      }
  
      @Override
      public String getName() {
          return _name;
      }
  
      @Override
      public Type getType() {
          return Host.Type.L2Networking;
      }
  
      @Override
      public StartupCommand[] initialize() {
          StartupBigSwitchBcfCommand sc = new StartupBigSwitchBcfCommand();
          sc.setGuid(_guid);
          sc.setName(_name);
          sc.setDataCenter(_zoneId);
          sc.setPod("");
          sc.setPrivateIpAddress("");
          sc.setStorageIpAddress("");
          sc.setVersion("");
          return new StartupCommand[] {sc};
      }
  
      @Override
      public PingCommand getCurrentStatus(long id) {
          if(!initTopologySyncDone && _latestTopology != null){
              initTopologySyncDone = true;
              if(_bigswitchBcfApi.isNatEnabled()){
                  try{
                      executeRequest(new SyncBcfTopologyCommand(true, true), _numRetries);
                  } catch(Exception e){
                      logger.error("BigSwitch BCF sync error", e);
                  }
              } else {
                  try{
                      executeRequest(new SyncBcfTopologyCommand(true, false), _numRetries);
                  } catch (Exception e){
                      logger.error("BigSwitch BCF sync error", e);
                  }
              }
          }
          try {
              ControlClusterStatus ccs = _bigswitchBcfApi.getControlClusterStatus();
              if (!ccs.getStatus()) {
                  logger.error("ControlCluster state is not ready: " + ccs.getStatus());
                  return null;
              }
              if (ccs.isTopologySyncRequested()) {
                  if(_latestTopology != null) {
                      if(_bigswitchBcfApi.isNatEnabled()){
                          executeRequest(new SyncBcfTopologyCommand(true, true), _numRetries);
                      } else {
                          executeRequest(new SyncBcfTopologyCommand(true, false), _numRetries);
                      }
                  } else {
                      logger.debug("topology sync needed but no topology history");
                  }
              }
          } catch (BigSwitchBcfApiException e) {
              logger.error("getControlClusterStatus failed", e);
              return null;
          }
          try {
              Capabilities cap = _bigswitchBcfApi.getCapabilities();
  
              // TODO: update controller status display, enable/disable service accordingly
              if (cap.isTopologySyncRequested()) {
                  if(_latestTopology != null) {
                      if(_bigswitchBcfApi.isNatEnabled()){
                          executeRequest(new SyncBcfTopologyCommand(true, true), _numRetries);
                      } else {
                          executeRequest(new SyncBcfTopologyCommand(true, false), _numRetries);
                      }
                  }
              }
  
          } catch (BigSwitchBcfApiException e) {
              logger.error("getCapabilities failed", e);
          }
          return new PingCommand(Host.Type.L2Networking, id);
      }
  
      @Override
      public Answer executeRequest(Command cmd) {
          return executeRequest(cmd, _numRetries);
      }
  
      public Answer executeRequest(Command cmd, int numRetries) {
          if (cmd instanceof ReadyCommand) {
              return executeRequest((ReadyCommand)cmd);
          } else if (cmd instanceof MaintainCommand) {
              return executeRequest((MaintainCommand)cmd);
          } else if (cmd instanceof CreateBcfSegmentCommand) {
              _latestTopology = ((CreateBcfSegmentCommand) cmd).getTopology();
              return executeRequest((CreateBcfSegmentCommand)cmd, numRetries);
          } else if (cmd instanceof DeleteBcfSegmentCommand) {
              _latestTopology = ((DeleteBcfSegmentCommand) cmd).getTopology();
              return executeRequest((DeleteBcfSegmentCommand)cmd, numRetries);
          } else if (cmd instanceof CreateBcfAttachmentCommand) {
              _latestTopology = ((CreateBcfAttachmentCommand) cmd).getTopology();
              return executeRequest((CreateBcfAttachmentCommand)cmd, numRetries);
          } else if (cmd instanceof DeleteBcfAttachmentCommand) {
              _latestTopology = ((DeleteBcfAttachmentCommand) cmd).getTopology();
              return executeRequest((DeleteBcfAttachmentCommand)cmd, numRetries);
          } else if (cmd instanceof UpdateBcfAttachmentCommand) {
              _latestTopology = ((UpdateBcfAttachmentCommand) cmd).getTopology();
              return executeRequest((UpdateBcfAttachmentCommand)cmd, numRetries);
          } else if (cmd instanceof CreateBcfRouterCommand) {
              _latestTopology = ((CreateBcfRouterCommand) cmd).getTopology();
              return executeRequest((CreateBcfRouterCommand)cmd, numRetries);
          } else if (cmd instanceof CreateBcfRouterInterfaceCommand) {
              _latestTopology = ((CreateBcfRouterInterfaceCommand) cmd).getTopology();
              return executeRequest((CreateBcfRouterInterfaceCommand)cmd, numRetries);
          } else if (cmd instanceof CreateBcfStaticNatCommand) {
              _latestTopology = ((CreateBcfStaticNatCommand) cmd).getTopology();
              return executeRequest((CreateBcfStaticNatCommand)cmd, numRetries);
          } else if (cmd instanceof DeleteBcfStaticNatCommand) {
              _latestTopology = ((DeleteBcfStaticNatCommand) cmd).getTopology();
              return executeRequest((DeleteBcfStaticNatCommand)cmd, numRetries);
          } else if (cmd instanceof UpdateBcfRouterCommand) {
              _latestTopology = ((UpdateBcfRouterCommand) cmd).getTopology();
              return executeRequest((UpdateBcfRouterCommand)cmd, numRetries);
          } else if (cmd instanceof SyncBcfTopologyCommand) {
              return executeRequest((SyncBcfTopologyCommand)cmd, numRetries);
          } else if (cmd instanceof CacheBcfTopologyCommand) {
              return executeRequest((CacheBcfTopologyCommand)cmd, numRetries);
          } else if (cmd instanceof GetControllerDataCommand) {
              return executeRequest((GetControllerDataCommand)cmd, numRetries);
          }
          logger.debug("Received unsupported command " + cmd.toString());
          return Answer.createUnsupportedCommandAnswer(cmd);
      }
  
      @Override
      public void disconnected() {
      }
  
      @Override
      public IAgentControl getAgentControl() {
          return null;
      }
  
      @Override
      public void setAgentControl(IAgentControl agentControl) {
      }
  
      public void setTopology(TopologyData topology){
          _latestTopology = topology;
      }
  
      public TopologyData getTopology(){
          return _latestTopology;
      }
  
      private Answer executeRequest(CreateBcfSegmentCommand cmd, int numRetries) {
          NetworkData network = new NetworkData();
          network.getNetwork().setTenantId(cmd.getTenantId());
          network.getNetwork().setTenantName(cmd.getTenantName());
          network.getNetwork().setId(cmd.getNetworkId());
          network.getNetwork().setName(truncate("segment-cloudstack-" + cmd.getNetworkName(), 64));
          network.getNetwork().setVlan(cmd.getVlan());
  
          try {
              String hash =_bigswitchBcfApi.createNetwork(network);
              return new BcfAnswer(cmd, true, "Segment " + network.getNetwork().getId() + " created", hash);
          } catch (BigSwitchBcfApiException e) {
              if (e.is_topologySyncRequested()) {
                  cmd.setTopologySyncRequested(true);
                  return new BcfAnswer(cmd, true, "Segment " + network.getNetwork().getId() + " created; topology sync required.");
              } else {
                  if (numRetries > 0) {
                      return retry(cmd, --numRetries);
                  } else {
                      return new BcfAnswer(cmd, e);
                  }
              }
          }
  
      }
  
      private Answer executeRequest(DeleteBcfSegmentCommand cmd, int numRetries) {
          try {
              String hash = _bigswitchBcfApi.deleteNetwork(cmd.getTenantUuid(), cmd.getNetworkUuid());
              return new BcfAnswer(cmd, true, "Segment " + cmd.getNetworkUuid() + " deleted", hash);
          } catch (BigSwitchBcfApiException e) {
              if (e.is_topologySyncRequested()) {
                  cmd.setTopologySyncRequested(true);
                  return new BcfAnswer(cmd, true, "Segment " + cmd.getNetworkUuid() + " deleted; topology sync required.");
              } else {
                  if (numRetries > 0) {
                      return retry(cmd, --numRetries);
                  } else {
                      return new BcfAnswer(cmd, e);
                  }
              }
          }
      }
  
      private Answer executeRequest(CreateBcfAttachmentCommand cmd, int numRetries) {
          AttachmentData attachment = new AttachmentData();
          attachment.getAttachment().setId(cmd.getNicId());
          attachment.getAttachment().setHostId(cmd.getPortId());
          attachment.getAttachment().setTenantName(cmd.getTenantName());
          attachment.getAttachment().setVlan(cmd.getVlan());
          attachment.getAttachment().addIpv4(cmd.getIpv4());
          attachment.getAttachment().setMac(cmd.getMac());
  
          try {
              String hash = _bigswitchBcfApi.createAttachment(cmd.getTenantId(), cmd.getNetworkId(), attachment);
              return new BcfAnswer(cmd, true, "network attachment " + cmd.getPortId() + " created", hash);
          } catch (BigSwitchBcfApiException e) {
              if (e.is_topologySyncRequested()) {
                  cmd.setTopologySyncRequested(true);
                  return new BcfAnswer(cmd, true, "network attachment " + cmd.getPortId() + " created; topology sync required.");
              } else {
                  if (numRetries > 0) {
                      return retry(cmd, --numRetries);
                  } else {
                      return new BcfAnswer(cmd, e);
                  }
              }
          }
      }
  
      private Answer executeRequest(DeleteBcfAttachmentCommand cmd, int numRetries) {
          String nicName = cmd.getAttachmentId();
          try {
              String hash = _bigswitchBcfApi.deleteAttachment(cmd.getTenantId(), cmd.getNetworkId(), nicName);
              return new BcfAnswer(cmd, true, "network attachment " + nicName + " deleted", hash);
          } catch (BigSwitchBcfApiException e) {
              if (e.is_topologySyncRequested()) {
                  cmd.setTopologySyncRequested(true);
                  return new BcfAnswer(cmd, true, "network attachment " + nicName + " deleted; topology sync required.");
              } else {
                  if (numRetries > 0) {
                      return retry(cmd, --numRetries);
                  } else {
                      return new BcfAnswer(cmd, e);
                  }
              }
          }
      }
  
      private Answer executeRequest(UpdateBcfAttachmentCommand cmd, int numRetries) {
          AttachmentData attachment = new AttachmentData();
          attachment.getAttachment().setId(cmd.getAttachmentId());
          attachment.getAttachment().setTenantName(cmd.getTenantId());
  
          try {
              String hash = _bigswitchBcfApi.modifyAttachment(cmd.getTenantId(), cmd.getNetworkId(), attachment);
              return new BcfAnswer(cmd, true, "Network attachment  " + cmd.getAttachmentId() + " updated", hash);
          } catch (BigSwitchBcfApiException e) {
              if (e.is_topologySyncRequested()) {
                  cmd.setTopologySyncRequested(true);
                  return new BcfAnswer(cmd, true, "Network attachment  " + cmd.getAttachmentId() + " updated; topology sync required.");
              } else {
                  if (numRetries > 0) {
                      return retry(cmd, --numRetries);
                  } else {
                      return new BcfAnswer(cmd, e);
                  }
              }
          }
      }
  
      private Answer executeRequest(CreateBcfStaticNatCommand cmd, int numRetries) {
          FloatingIpData fip = new FloatingIpData();
          fip.setTenantId(cmd.getTenantId());
          fip.setNetworkId(cmd.getNetworkId());
          fip.setFixedIp(cmd.getPrivateIp());
          fip.setFloatingIpAndId(cmd.getPublicIp());
          fip.setMac(cmd.getMac());
  
          try {
              String hash = _bigswitchBcfApi.createFloatingIp(cmd.getTenantId(), fip);
              return new BcfAnswer(cmd, true, "floating ip " + cmd.getPublicIp() + ":" +
                      cmd.getPrivateIp() + " created", hash);
          } catch (BigSwitchBcfApiException e) {
              if (e.is_topologySyncRequested()) {
                  cmd.setTopologySyncRequested(true);
                  return new BcfAnswer(cmd, true, "floating ip " + cmd.getPublicIp() + ":" +
                          cmd.getPrivateIp() + " created; topology sync required.");
              } else {
                  if (numRetries > 0) {
                      return retry(cmd, --numRetries);
                  } else {
                      return new BcfAnswer(cmd, e);
                  }
              }
          }
      }
  
      private Answer executeRequest(DeleteBcfStaticNatCommand cmd, int numRetries) {
          try {
              String hash = _bigswitchBcfApi.deleteFloatingIp(cmd.getTenantId(), cmd.getFloatingIpId());
              return new BcfAnswer(cmd, true, "floating ip " + cmd.getPublicIp() + " deleted", hash);
          } catch (BigSwitchBcfApiException e) {
              if (e.is_topologySyncRequested()) {
                  cmd.setTopologySyncRequested(true);
                  return new BcfAnswer(cmd, true, "floating ip " + cmd.getPublicIp() + " deleted; topology sync required.");
              } else {
                  if (numRetries > 0) {
                      return retry(cmd, --numRetries);
                  } else {
                      return new BcfAnswer(cmd, e);
                  }
              }
          }
      }
  
      private Answer executeRequest(CreateBcfRouterCommand cmd, int numRetries) {
          RouterData router = new RouterData(cmd.getTenantId());
          try {
              String hash;
              hash = _bigswitchBcfApi.createRouter(cmd.getTenantId(), router);
  
              return new BcfAnswer(cmd, true, "router " + cmd.getTenantId() +
                      " created.", hash);
          } catch (BigSwitchBcfApiException e) {
              if (e.is_topologySyncRequested()) {
                  cmd.setTopologySyncRequested(true);
                  return new BcfAnswer(cmd, true, " created; topology sync required.");
              } else {
                  if (numRetries > 0) {
                      return retry(cmd, --numRetries);
                  } else {
                      return new BcfAnswer(cmd, e);
                  }
              }
          }
      }
  
      private Answer executeRequest(CreateBcfRouterInterfaceCommand cmd, int numRetries) {
          RouterInterfaceData routerInterface = new RouterInterfaceData(cmd.getTenantId(),
                  cmd.getGateway(), cmd.getCidr(), cmd.getNetworkId(), cmd.getNetworkName());
          try {
              String hash;
              hash = _bigswitchBcfApi.createRouterInterface(cmd.getTenantId(),
                      cmd.getTenantId(), routerInterface);
  
              return new BcfAnswer(cmd, true, "router " + cmd.getTenantId() +
                      " created.", hash);
          } catch (BigSwitchBcfApiException e) {
              if (e.is_topologySyncRequested()) {
                  cmd.setTopologySyncRequested(true);
                  return new BcfAnswer(cmd, true, " created; topology sync required.");
              } else {
                  if (numRetries > 0) {
                      return retry(cmd, --numRetries);
                  } else {
                      return new BcfAnswer(cmd, e);
                  }
              }
          }
      }
  
      private Answer executeRequest(UpdateBcfRouterCommand cmd, int numRetries){
          RouterData routerData = new RouterData(cmd.getTenantId());
  
          List<AclData> acls = new ArrayList<AclData>();
          acls.addAll(cmd.getAcls());
          routerData.getRouter().getAcls().addAll(acls);
  
          routerData.getRouter().addExternalGateway(cmd.getPublicIp());
  
          try {
              String hash = _bigswitchBcfApi.modifyRouter(cmd.getTenantId(), routerData);
              return new BcfAnswer(cmd, true, "tenant " + cmd.getTenantId() + " router updated", hash);
          } catch (BigSwitchBcfApiException e) {
              if (e.is_topologySyncRequested()) {
                  cmd.setTopologySyncRequested(true);
                  return new BcfAnswer(cmd, true, "tenant " + cmd.getTenantId() + " router updated but topology sync required.");
              } else {
                  if (numRetries > 0) {
                      return retry(cmd, --numRetries);
                  } else {
                      return new BcfAnswer(cmd, e);
                  }
              }
          } catch (IllegalArgumentException e1){
              return new BcfAnswer(cmd, false, "Illegal argument in BCF router update");
          }
      }
  
      private Answer executeRequest(SyncBcfTopologyCommand cmd, int numRetries) {
          try {
              TopologyData topo = _latestTopology;
              if (!cmd.isNetworkIncluded()) {
                  topo.clearNetworks();
              }
              if(!cmd.isRouterIncluded()) {
                  topo.clearRouters();
              }
              String hash = _bigswitchBcfApi.syncTopology(topo);
              if(!initTopologySyncDone){
                  initTopologySyncDone=true;
              }
              return new BcfAnswer(cmd, true, "BCF topology synced", hash);
          } catch (BigSwitchBcfApiException e) {
              if (numRetries > 0) {
                  return retry(cmd, --numRetries);
              } else {
                  return new BcfAnswer(cmd, e);
              }
          } catch (IllegalArgumentException e1){
              return new BcfAnswer(cmd, false, "Illegal argument in BCF topology sync");
          }
      }
  
      private Answer executeRequest(CacheBcfTopologyCommand cmd, int numRetries) {
          _latestTopology = cmd.getTopology();
          return new Answer(cmd, true, "BCF topology cached");
      }
  
      private Answer executeRequest(GetControllerDataCommand cmd, int numRetries) {
          ControllerData controller = _bigswitchBcfApi.getControllerData();
          return new GetControllerDataAnswer(cmd,
                  controller.getIpAddress(),
                  controller.isPrimary());
      }
  
      private Answer executeRequest(ReadyCommand cmd) {
          return new ReadyAnswer(cmd);
      }
  
      private Answer executeRequest(MaintainCommand cmd) {
          return new MaintainAnswer(cmd);
      }
  
      private Answer retry(Command cmd, int numRetries) {
          logger.warn("Retrying " + cmd.getClass().getSimpleName() + ". Number of retries remaining: " + numRetries);
          return executeRequest(cmd, numRetries);
      }
  
      private String truncate(String string, int length) {
          if (string.length() <= length) {
              return string;
          } else {
              return string.substring(0, length);
          }
      }
  }
  