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.brocade;
  
  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.InputStreamReader;
  import java.io.StringReader;
  import java.io.StringWriter;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.nio.charset.Charset;
  
  import javax.xml.bind.JAXBContext;
  import javax.xml.bind.JAXBException;
  import javax.xml.bind.Marshaller;
  import javax.xml.bind.Unmarshaller;
  
  import org.apache.http.HttpResponse;
  import org.apache.http.HttpStatus;
  import org.apache.http.auth.AuthScope;
  import org.apache.http.auth.UsernamePasswordCredentials;
  import org.apache.http.client.methods.HttpDelete;
  import org.apache.http.client.methods.HttpGet;
  import org.apache.http.client.methods.HttpPatch;
  import org.apache.http.client.methods.HttpPost;
  import org.apache.http.client.methods.HttpRequestBase;
  import org.apache.http.entity.ContentType;
  import org.apache.http.entity.StringEntity;
  import org.apache.http.impl.client.DefaultHttpClient;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  
  import com.cloud.network.schema.interfacevlan.InterfaceVlan;
  import com.cloud.network.schema.interfacevlan.Interface;
  import com.cloud.network.schema.interfacevlan.Vlan;
  import com.cloud.network.schema.portprofile.PortProfile;
  import com.cloud.network.schema.portprofile.PortProfile.Activate;
  import com.cloud.network.schema.portprofile.PortProfile.Static;
  import com.cloud.network.schema.portprofile.PortProfileGlobal;
  import com.cloud.network.schema.portprofile.VlanProfile;
  import com.cloud.network.schema.portprofile.VlanProfile.Switchport;
  import com.cloud.network.schema.portprofile.VlanProfile.Switchport.Mode;
  import com.cloud.network.schema.portprofile.VlanProfile.Switchport.Trunk;
  import com.cloud.network.schema.portprofile.VlanProfile.Switchport.Trunk.Allowed;
  import com.cloud.network.schema.portprofile.VlanProfile.SwitchportBasic;
  import com.cloud.network.schema.portprofile.VlanProfile.SwitchportBasic.Basic;
  import com.cloud.network.schema.showvcs.Output;
  
  public class BrocadeVcsApi {
      protected Logger logger = LogManager.getLogger(getClass());
  
      private final String _host;
      private final String _adminuser;
      private final String _adminpass;
  
      protected DefaultHttpClient _client;
  
      protected HttpRequestBase createMethod(String type, String uri) throws BrocadeVcsApiException {
          String url;
          try {
              url = new URL(Constants.PROTOCOL, _host, Constants.PORT, uri).toString();
          } catch (final MalformedURLException e) {
              logger.error("Unable to build Brocade Switch API URL", e);
              throw new BrocadeVcsApiException("Unable to build Brocade Switch API URL", e);
          }
  
          if ("post".equalsIgnoreCase(type)) {
              return new HttpPost(url);
          } else if ("get".equalsIgnoreCase(type)) {
              return new HttpGet(url);
          } else if ("delete".equalsIgnoreCase(type)) {
              return new HttpDelete(url);
          } else if ("patch".equalsIgnoreCase(type)) {
              return new HttpPatch(url);
          } else {
              throw new BrocadeVcsApiException("Requesting unknown method type");
          }
      }
  
      public BrocadeVcsApi(String address, String username, String password) {
          _host = address;
          _adminuser = username;
          _adminpass = password;
          _client = new DefaultHttpClient();
          _client.getCredentialsProvider().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(_adminuser, _adminpass));
  
      }
  
      /*
       * Get Operational Status
       */
      public Output getSwitchStatus() throws BrocadeVcsApiException {
          return executeRetreiveStatus(Constants.STATUS_URI);
  
      }
  
      /*
       * Creates a new virtual network.
       */
      public boolean createNetwork(int vlanId, long networkId) throws BrocadeVcsApiException {
  
          if (createInterfaceVlan(vlanId)) {
  
              final PortProfile portProfile = createPortProfile(vlanId, networkId);
  
              if (portProfile != null) {
                  return activatePortProfile(portProfile);
              }
          }
          return false;
      }
  
      /*
       * Activates a port-profile.
       */
      private boolean activatePortProfile(PortProfile portProfile) throws BrocadeVcsApiException {
          final PortProfileGlobal portProfileGlobal = new PortProfileGlobal();
          portProfile.setVlanProfile(null);
          final Activate activate = new Activate();
          portProfile.setActivate(activate);
          portProfileGlobal.setPortProfile(portProfile);
  
          //activate port-profile
          return executeUpdateObject(portProfileGlobal, Constants.URI);
      }
  
      /*
       *  Creates AMPP port-profile.
       */
      private PortProfile createPortProfile(int vlanId, long networkId) throws BrocadeVcsApiException {
  
          final PortProfile portProfile = new PortProfile();
          portProfile.setName(Constants.PORT_PROFILE_NAME_PREFIX + networkId);
          if (executeCreateObject(portProfile, Constants.URI)) {
              if (createVlanSubProfile(vlanId, portProfile)) {
                  return portProfile;
              }
          }
          return null;
      }
  
      /*
       * Create vlan sub-profile for port-profile
       */
      private boolean createVlanSubProfile(int vlanId, PortProfile portProfile) throws BrocadeVcsApiException {
          final VlanProfile vlanProfile = new VlanProfile();
          portProfile.setVlanProfile(vlanProfile);
          if (executeUpdateObject(portProfile, Constants.URI)) {
              return configureVlanSubProfile(vlanId, portProfile);
          }
          return false;
      }
  
      /*
       * Configures vlan sub-profile for port-profile.
       * - configure L2 mode for vlan sub-profile
       * - configure trunk mode for vlan sub-profile
       * - configure allowed VLANs for vlan sub-profile
       */
      private boolean configureVlanSubProfile(int vlanId, PortProfile portProfile) throws BrocadeVcsApiException {
          final SwitchportBasic switchPortBasic = new SwitchportBasic();
          final Basic basic = new Basic();
          switchPortBasic.setBasic(basic);
          portProfile.getVlanProfile().setSwitchportBasic(switchPortBasic);
          // configure L2 mode for vlan sub-profile
          if (executeUpdateObject(portProfile, Constants.URI)) {
              VlanProfile vlanProfile = new VlanProfile();
              Switchport switchPort = new Switchport();
              final Mode mode = new Mode();
              mode.setVlanMode("trunk");
              switchPort.setMode(mode);
              vlanProfile.setSwitchport(switchPort);
              portProfile.setVlanProfile(vlanProfile);
  
              // configure trunk mode for vlan sub-profile
              if (executeUpdateObject(portProfile, Constants.URI)) {
                  vlanProfile = new VlanProfile();
                  switchPort = new Switchport();
                  final Trunk trunk = new Trunk();
                  final Allowed allowed = new Allowed();
                  final Allowed.Vlan allowedVlan = new Allowed.Vlan();
                  allowedVlan.setAdd(vlanId);
                  allowed.setVlan(allowedVlan);
                  trunk.setAllowed(allowed);
                  switchPort.setTrunk(trunk);
                  vlanProfile.setSwitchport(switchPort);
                  portProfile.setVlanProfile(vlanProfile);
  
                  //configure allowed VLANs for vlan sub-profile
                  return executeUpdateObject(portProfile, Constants.URI);
              }
          }
  
          return false;
  
      }
  
      /*
       * Creates a vlan interface.
       */
      private boolean createInterfaceVlan(int vlanId) throws BrocadeVcsApiException {
          final InterfaceVlan interfaceVlan = new InterfaceVlan();
          final Interface interfaceObj = new Interface();
          final Vlan vlan = new Vlan();
          vlan.setName(vlanId);
          interfaceObj.setVlan(vlan);
          interfaceVlan.setInterface(interfaceObj);
  
          return executeUpdateObject(interfaceVlan, Constants.URI);
  
      }
  
      /*
       * Associates a MAC address to virtual network.
       */
      public boolean associateMacToNetwork(long networkId, String macAddress) throws BrocadeVcsApiException {
  
          final PortProfileGlobal portProfileGlobal = new PortProfileGlobal();
          final PortProfile portProfile = new PortProfile();
          portProfile.setName(Constants.PORT_PROFILE_NAME_PREFIX + networkId);
          final Static staticObj = new Static();
          staticObj.setMacAddress(macAddress);
          portProfile.setStatic(staticObj);
          portProfileGlobal.setPortProfile(portProfile);
  
          //associates a mac address to a port-profile
          return executeUpdateObject(portProfileGlobal, Constants.URI);
      }
  
      /*
       * Disassociates a MAC address from virtual network.
       */
      public boolean disassociateMacFromNetwork(long networkId, String macAddress) throws BrocadeVcsApiException {
  
          final PortProfileGlobal portProfileGlobal = new PortProfileGlobal();
          final PortProfile portProfile = new PortProfile();
          portProfile.setName(Constants.PORT_PROFILE_NAME_PREFIX + networkId);
          final Static staticObj = new Static();
          staticObj.setOperation("delete");
          staticObj.setMacAddress(macAddress);
          portProfile.setStatic(staticObj);
          portProfileGlobal.setPortProfile(portProfile);
  
          //associates a mac address to a port-profile
          return executeUpdateObject(portProfileGlobal, Constants.URI);
      }
  
      /*
       * Deletes a new virtual network.
       */
      public boolean deleteNetwork(int vlanId, long networkId) throws BrocadeVcsApiException {
  
          if (deactivatePortProfile(networkId)) {
  
              if (deletePortProfile(networkId)) {
                  return deleteInterfaceVlan(vlanId);
              }
          }
          return false;
      }
  
      /*
       * Deletes a vlan interface.
       */
      private boolean deleteInterfaceVlan(int vlanId) throws BrocadeVcsApiException {
          final InterfaceVlan interfaceVlan = new InterfaceVlan();
          final Interface interfaceObj = new Interface();
          final Vlan vlan = new Vlan();
          vlan.setOperation("delete");
          vlan.setName(vlanId);
          interfaceObj.setVlan(vlan);
          interfaceVlan.setInterface(interfaceObj);
  
          return executeUpdateObject(interfaceVlan, Constants.URI);
  
      }
  
      /*
       * Deactivates a port-profile.
       */
      private boolean deactivatePortProfile(long networkId) throws BrocadeVcsApiException {
          final PortProfileGlobal portProfileGlobal = new PortProfileGlobal();
          final PortProfile portProfile = new PortProfile();
          portProfile.setName(Constants.PORT_PROFILE_NAME_PREFIX + networkId);
          final Activate activate = new Activate();
          activate.setOperation("delete");
          portProfile.setActivate(activate);
          portProfileGlobal.setPortProfile(portProfile);
  
          //activate port-profile
          return executeUpdateObject(portProfileGlobal, Constants.URI);
      }
  
      /*
       *  Deletes AMPP port-profile.
       */
      private boolean deletePortProfile(long networkId) throws BrocadeVcsApiException {
  
          final PortProfile portProfile = new PortProfile();
          portProfile.setName(Constants.PORT_PROFILE_NAME_PREFIX + networkId);
          portProfile.setOperation("delete");
          //deletes port-profile
          return executeUpdateObject(portProfile, Constants.URI);
      }
  
      protected <T> boolean executeUpdateObject(T newObject, String uri) throws BrocadeVcsApiException {
  
          final boolean result = true;
  
          if (_host == null || _host.isEmpty() || _adminuser == null || _adminuser.isEmpty() || _adminpass == null || _adminpass.isEmpty()) {
              throw new BrocadeVcsApiException("Hostname/credentials are null or empty");
          }
  
          final HttpPatch pm = (HttpPatch)createMethod("patch", uri);
          pm.setHeader("Accept", "application/vnd.configuration.resource+xml");
  
          pm.setEntity(new StringEntity(convertToString(newObject), ContentType.APPLICATION_XML));
  
          final HttpResponse response = executeMethod(pm);
  
          if (response.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) {
  
              String errorMessage;
              try {
                  errorMessage = responseToErrorMessage(response);
              } catch (final IOException e) {
                  logger.error("Failed to update object : " + e.getMessage());
                  throw new BrocadeVcsApiException("Failed to update object : " + e.getMessage());
              }
  
              pm.releaseConnection();
              logger.error("Failed to update object : " + errorMessage);
              throw new BrocadeVcsApiException("Failed to update object : " + errorMessage);
          }
  
          pm.releaseConnection();
  
          return result;
      }
  
      protected <T> String convertToString(T object) throws BrocadeVcsApiException {
  
          final StringWriter stringWriter = new StringWriter();
  
          try {
              final JAXBContext context = JAXBContext.newInstance(object.getClass());
              final Marshaller marshaller = context.createMarshaller();
  
              marshaller.marshal(object, stringWriter);
  
          } catch (final JAXBException e) {
              logger.error("Failed to convert object to string : " + e.getMessage());
              throw new BrocadeVcsApiException("Failed to convert object to string : " + e.getMessage());
          }
  
          final String str = stringWriter.toString();
          logger.info(str);
  
          return str;
  
      }
  
      protected Output convertToXML(String object) throws BrocadeVcsApiException {
  
          Output output = null;
          try {
              final JAXBContext context = JAXBContext.newInstance(Output.class);
  
              final StringReader reader = new StringReader(object);
  
              final Unmarshaller unmarshaller = context.createUnmarshaller();
              final Object result = unmarshaller.unmarshal(reader);
  
              if (result instanceof Output) {
                  output = (Output)result;
                  logger.info(output);
              }
  
          } catch (final JAXBException e) {
              logger.error("Failed to convert string to object : " + e.getMessage());
              throw new BrocadeVcsApiException("Failed to convert string to object : " + e.getMessage());
          }
  
          return output;
  
      }
  
      protected <T> boolean executeCreateObject(T newObject, String uri) throws BrocadeVcsApiException {
          if (_host == null || _host.isEmpty() || _adminuser == null || _adminuser.isEmpty() || _adminpass == null || _adminpass.isEmpty()) {
              throw new BrocadeVcsApiException("Hostname/credentials are null or empty");
          }
  
          final boolean result = true;
          final HttpPost pm = (HttpPost)createMethod("post", uri);
          pm.setHeader("Accept", "application/vnd.configuration.resource+xml");
          pm.setEntity(new StringEntity(convertToString(newObject), ContentType.APPLICATION_XML));
  
          final HttpResponse response = executeMethod(pm);
  
          if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
  
              String errorMessage;
              try {
                  errorMessage = responseToErrorMessage(response);
              } catch (final IOException e) {
                  logger.error("Failed to create object : " + e.getMessage());
                  throw new BrocadeVcsApiException("Failed to create object : " + e.getMessage());
              }
  
              pm.releaseConnection();
              logger.error("Failed to create object : " + errorMessage);
              throw new BrocadeVcsApiException("Failed to create object : " + errorMessage);
          }
  
          pm.releaseConnection();
  
          return result;
      }
  
      protected Output executeRetreiveStatus(String uri) throws BrocadeVcsApiException {
          if (_host == null || _host.isEmpty() || _adminuser == null || _adminuser.isEmpty() || _adminpass == null || _adminpass.isEmpty()) {
              throw new BrocadeVcsApiException("Hostname/credentials are null or empty");
          }
  
          String readLine = null;
          StringBuffer sb = null;
  
          final HttpPost pm = (HttpPost)createMethod("post", uri);
          pm.setHeader("Accept", "application/vnd.operational-state.resource+xml");
          pm.setEntity(new StringEntity("<show-vcs></show-vcs>", ContentType.APPLICATION_XML));
  
          final HttpResponse response = executeMethod(pm);
  
          if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
  
              String errorMessage;
              try {
                  errorMessage = responseToErrorMessage(response);
              } catch (final IOException e) {
                  logger.error("Failed to retreive status : " + e.getMessage());
                  throw new BrocadeVcsApiException("Failed to retreive status : " + e.getMessage());
              }
  
              pm.releaseConnection();
              logger.error("Failed to retreive status : " + errorMessage);
              throw new BrocadeVcsApiException("Failed to retreive status : " + errorMessage);
          }
  
          try (BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), Charset.forName("UTF-8")))) {
              sb = new StringBuffer();
  
              while ((readLine = br.readLine()) != null) {
                  logger.debug(readLine);
                  sb.append(readLine);
  
              }
          } catch (final Exception e) {
              logger.error("Failed to retreive status : " + e.getMessage());
              throw new BrocadeVcsApiException("Failed to retreive status : " + e.getMessage());
          }
  
          pm.releaseConnection();
  
          return convertToXML(sb.toString());
      }
  
      protected void executeDeleteObject(String uri) throws BrocadeVcsApiException {
          if (_host == null || _host.isEmpty() || _adminuser == null || _adminuser.isEmpty() || _adminpass == null || _adminpass.isEmpty()) {
              throw new BrocadeVcsApiException("Hostname/credentials are null or empty");
          }
  
          final HttpDelete dm = (HttpDelete)createMethod("delete", uri);
          dm.setHeader("Accept", "application/vnd.configuration.resource+xml");
  
          final HttpResponse response = executeMethod(dm);
  
          if (response.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) {
  
              String errorMessage;
              try {
                  errorMessage = responseToErrorMessage(response);
              } catch (final IOException e) {
                  logger.error("Failed to delete object : " + e.getMessage());
                  throw new BrocadeVcsApiException("Failed to delete object : " + e.getMessage());
              }
  
              dm.releaseConnection();
              logger.error("Failed to delete object : " + errorMessage);
              throw new BrocadeVcsApiException("Failed to delete object : " + errorMessage);
          }
          dm.releaseConnection();
      }
  
      protected HttpResponse executeMethod(HttpRequestBase method) throws BrocadeVcsApiException {
          HttpResponse response = null;
          try {
              response = _client.execute(method);
              if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
                  method.releaseConnection();
                  response = _client.execute(method);
              }
          } catch (final IOException e) {
              logger.error("IOException caught while trying to connect to the Brocade Switch", e);
              method.releaseConnection();
              throw new BrocadeVcsApiException("API call to Brocade Switch Failed", e);
          }
  
          return response;
      }
  
      private String responseToErrorMessage(HttpResponse response) throws IOException {
  
          if ("text/html".equals(response.getEntity().getContentType().getValue())) {
  
              try (BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), Charset.forName("UTF-8")))) {
  
                  final StringBuffer result = new StringBuffer();
                  String line = "";
                  while ((line = rd.readLine()) != null) {
                      result.append(line);
                  }
                  return result.toString();
              }
          }
          return null;
      }
  
  }
  