All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cisco.trex.stateful.TRexAstfClient Maven / Gradle / Ivy

There is a newer version: 1.69
Show newest version
package com.cisco.trex.stateful;

import com.cisco.trex.ClientBase;
import com.cisco.trex.stateful.model.ServerStatus;
import com.cisco.trex.stateful.model.stats.AstfStatistics;
import com.cisco.trex.stateful.model.stats.LatencyPortData;
import com.cisco.trex.stateful.model.stats.LatencyStats;
import com.cisco.trex.stateful.model.stats.MetaData;
import com.cisco.trex.stateless.exception.TRexConnectionException;
import com.cisco.trex.stateless.model.ApiVersionHandler;
import com.cisco.trex.stateless.model.TRexClientResult;
import com.cisco.trex.util.Constants;
import com.cisco.trex.util.TRexClientUtil;
import com.cisco.trex.util.TRexServerMode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.apache.commons.lang3.StringUtils;

/** TRex Astf Client class */
public class TRexAstfClient extends ClientBase {

  private static final String ASTF = "ASTF";
  private MetaData counterMetaData;

  /**
   * @param host
   * @param port
   * @param userName
   */
  public TRexAstfClient(String host, String port, String userName) {
    this.host = host;
    this.port = port;
    this.userName = userName;
    this.supportedCmds.add("api_sync_v2");
    this.supportedCmds.add("get_supported_cmds");
  }

  @Override
  protected void serverAPISync() throws TRexConnectionException {
    LOGGER.info("Sync API with the TRex");
    Map apiVers = new HashMap<>();
    int majorVersion = Constants.ASTF_API_VERSION_MAJOR;
    int minorVersion = Constants.ASTF_API_VERSION_MINOR;
    apiVers.put("major", majorVersion);
    apiVers.put("minor", minorVersion);
    apiVers.put("name", ASTF);
    TRexClientResult result =
        callMethod("api_sync_v2", apiVers, ApiVersionHandler.class);

    // Currently the TRex server has  the NBC  issue to uplift the  ASTF_API_VERSION_MAJOR version
    // This if-block is a temporary solution to support uplift the  ASTF_API_VERSION_MAJOR version ,
    // if the TRex server does not uplift its version ,the client will continue use the old api, if
    // the server uplift, the client will use the new api.
    if (!StringUtils.isBlank(result.getError()) && result.getError().contains("Version mismatch")) {
      String regrexString = "server: '([0-9]+)\\.([0-9]+)', client: '([0-9]+)\\.([0-9]+)'";
      Pattern pattern = Pattern.compile(regrexString);
      Matcher matcher = pattern.matcher(result.getError());
      if (matcher.find()) {
        majorVersion = Integer.parseInt(matcher.group(1));
        minorVersion = Integer.parseInt(matcher.group(2));
        if (!TRexClientUtil.isVersionCorrect(TRexServerMode.ASTF, majorVersion, minorVersion)) {
          new TRexConnectionException(
              "Unable to connect to TRex server. Required API version is "
                  + majorVersion
                  + "."
                  + minorVersion);
        }
        apiVers.put("major", majorVersion);
        apiVers.put("minor", minorVersion);
        result = callMethod("api_sync_v2", apiVers, ApiVersionHandler.class);
      }
    }

    if (result.get() == null) {
      TRexConnectionException e =
          new TRexConnectionException(
              "Unable to connect to TRex server. Required API version is "
                  + majorVersion
                  + "."
                  + minorVersion);
      LOGGER.error("Unable to sync client with TRex server due to: API_H is null.", e.getMessage());
      throw e;
    }
    this.apiH = result.get().getApiH();
    LOGGER.info("Received api_H: {}", apiH);
  }

  private Map createPayload() {
    Map payload = new HashMap<>();
    payload.put(API_H, apiH);
    if (!StringUtils.isEmpty(masterHandler)) {
      payload.put("handler", masterHandler);
    }
    return payload;
  }

  protected Map createPayload(String profileId) {
    Map payload = createPayload();
    if (profileId != null && !profileId.isEmpty()) {
      payload.put("profile_id", profileId);
    }
    return payload;
  }

  private static String calculateMd5(String profile) {
    try {
      MessageDigest md = MessageDigest.getInstance("MD5");
      byte[] hashInBytes = md.digest(profile.getBytes(StandardCharsets.UTF_8));
      StringBuilder sb = new StringBuilder();
      for (byte b : hashInBytes) {
        sb.append(String.format("%02x", b));
      }
      return sb.toString();
    } catch (NoSuchAlgorithmException e) {
      throw new IllegalStateException("Could not generate MD5", e);
    }
  }

  /**
   * start traffic on all ports on loaded profile associated with default profile id
   *
   * @param clientMask
   * @param duration
   * @param ipv6
   * @param latencyPps
   * @param mult
   * @param nc
   */
  public void startTraffic(
      long clientMask, double duration, boolean ipv6, int latencyPps, int mult, boolean nc) {
    startTraffic("", clientMask, duration, ipv6, latencyPps, mult, nc);
  }

  /**
   * start traffic on all ports on loaded profile associated with specified profile id
   *
   * @param profileId
   * @param clientMask
   * @param duration
   * @param ipv6
   * @param latencyPps
   * @param mult
   * @param nc
   */
  public void startTraffic(
      String profileId,
      long clientMask,
      double duration,
      boolean ipv6,
      int latencyPps,
      int mult,
      boolean nc) {
    Map payload = createPayload(profileId);
    payload.put("client_mask", clientMask);
    payload.put("duration", duration);
    payload.put("ipv6", ipv6);
    payload.put("latency_pps", latencyPps);
    payload.put("mult", mult);
    payload.put("nc", nc);
    this.callMethod("start", payload);
  }

  /**
   * start latency ICMP streams in Rx core
   *
   * @param mask
   * @param mult
   * @param srcAddr
   * @param dstAddr
   * @param dualPortAddr
   */
  public void startLatencyTraffic(
      long mask, int mult, String srcAddr, String dstAddr, String dualPortAddr) {
    Map payload = createPayload();
    payload.put("mask", mask);
    payload.put("mult", mult);
    payload.put("src_addr", srcAddr);
    payload.put("dst_addr", dstAddr);
    payload.put("dual_port_addr", dualPortAddr);
    this.callMethod("start_latency", payload);
  }

  /** Stop the active traffic associated with default profile id */
  public void stopTraffic() {
    stopTraffic("");
  }

  /**
   * Stop the active traffic associated with specified profile id
   *
   * @param profileId
   */
  public void stopTraffic(String profileId) {
    Map payload = createPayload(profileId);
    this.callMethod("stop", payload);
  }

  /** Stop all active traffic */
  public void stopAllTraffic() {
    List profileIds = getProfileIds();
    for (String profileId : profileIds) {
      stopTraffic(profileId);
    }
  }

  /** Stop active latency traffic */
  public void stopLatencyTraffic() {
    Map payload = createPayload();
    this.callMethod("stop_latency", payload);
  }

  /**
   * Update the multiplier of running traffic
   *
   * @param mult
   */
  public void updateTrafficRate(int mult) {
    Map payload = createPayload();
    payload.put("mult", mult);
    this.callMethod("update", payload);
  }

  /**
   * Update the multiplier of running traffic
   *
   * @param mult
   */
  public void updateLatencyTrafficRate(int mult) {
    Map payload = createPayload();
    payload.put("mult", mult);
    this.callMethod("update_latency", payload);
  }

  /**
   * In ASTF mode all ports will be acquired in a single call, not support to acquire a single port
   *
   * @param force
   */
  public void acquirePorts(Boolean force) {
    Map payload = createPayload();
    payload.put("session_id", new Random().nextInt(Integer.MAX_VALUE - 1) + 1);
    payload.put("user", userName);
    payload.put("force", force);
    String json = callMethod("acquire", payload);
    Set> entrySet;
    try {
      this.masterHandler =
          getResultFromResponse(json).getAsJsonObject().get("handler").getAsString();
      entrySet =
          getResultFromResponse(json).getAsJsonObject().get("ports").getAsJsonObject().entrySet();
    } catch (NullPointerException e) {
      throw new IllegalStateException(
          "could not parse attribute, please release the port first", e);
    }
    if (force) {
      portHandlers.clear();
    }
    for (Entry entry : entrySet) {
      portHandlers.put(Integer.parseInt(entry.getKey()), entry.getValue().getAsString());
    }
    LOGGER.info("portHandlers is: {} ", portHandlers);
  }

  /** Release Ports */
  public void releasePorts() {
    if (StringUtils.isEmpty(masterHandler)) {
      LOGGER.debug("No handler assigned, ports are not acquired.");
    } else {
      Map payload = createPayload();
      payload.put("user", userName);
      String result = callMethod("release", payload);
      if (result.contains("must acquire the context")) {
        LOGGER.info("Ports are not owned by this session, already released or never acquired");
      }
      portHandlers.clear();
    }
  }

  /**
   * Load profile object as string and upload in fragments
   *
   * @param profile
   */
  public void loadProfile(String profile) {
    loadProfile("", profile);
  }

  /**
   * Load profile object as string and upload in fragments and associate it with specified profile
   * id
   *
   * @param profile
   * @param profileId
   */
  public void loadProfile(String profileId, String profile) {
    int indexStart = 0;
    int fragmentLength = 1000; // shorter length the first time
    int totalLength = profile.length();
    while (totalLength > indexStart) {
      int indexEnd = indexStart + fragmentLength;
      Map payload = createPayload(profileId);
      if (indexStart == 0) { // is first fragment
        payload.put("frag_first", true);
        payload.put("total_size", totalLength);
        payload.put("md5", calculateMd5(profile));
      }
      if (indexEnd >= totalLength) {
        payload.put("frag_last", true);
        indexEnd = totalLength;
      }
      payload.put("fragment", profile.subSequence(indexStart, indexEnd));
      this.callMethod("profile_fragment", payload);
      indexStart = indexEnd;
      fragmentLength = 500000; // larger fragments after first fragment
    }
  }

  /** clear profile on loaded state for default profile id */
  public void clearProfile() {
    clearProfile("");
  }

  /**
   * clear profile on loaded state for specified profile id
   *
   * @param profileId
   */
  public void clearProfile(String profileId) {
    Map payload = createPayload(profileId);
    this.callMethod("profile_clear", payload);
  }

  /**
   * fetch all the associated profile ids
   *
   * @return profile id list
   */
  public List getProfileIds() {
    if (StringUtils.isEmpty(masterHandler)) {
      return Collections.emptyList();
    }
    Map payload = createPayload();
    String json = callMethod("get_profile_list", payload);
    JsonArray ids = getResultFromResponse(json).getAsJsonArray();
    return StreamSupport.stream(ids.spliterator(), false)
        .map(JsonElement::getAsString)
        .collect(Collectors.toList());
  }

  /**
   * Get ASTF counters of profile associated with specified profile id
   *
   * @param profileId
   * @return AstfStatistics
   */
  public AstfStatistics getAstfStatistics(String profileId) {
    Map payload = createPayload(profileId);
    return callMethod("get_counter_values", payload, AstfStatistics.class)
        .get()
        .setCounterNames(getAstfStatsMetaData());
  }

  /**
   * Get ASTF total counters for all profiles
   *
   * @return AstfStatistics
   */
  public AstfStatistics getAstfTotalStatistics() {
    Map payload = createPayload();
    return callMethod("get_total_counter_values", payload, AstfStatistics.class)
        .get()
        .setCounterNames(getAstfStatsMetaData());
  }

  private MetaData getAstfStatsMetaData() {
    if (counterMetaData == null) {
      Map payload = createPayload();
      counterMetaData = callMethod("get_counter_desc", payload, MetaData.class).get();
    }

    return counterMetaData;
  }

  /**
   * Get Latency Stats
   *
   * @return LatencyStats
   */
  public LatencyStats getLatencyStats() {
    Map payload = this.createPayload();
    String json = this.callMethod("get_latency_stats", payload);
    JsonElement latencyStatsJsonElement = getResultFromResponse(json);
    // only can parse a part of data, LatencyPortData need to be parsed manually.
    LatencyStats latencyStats = GSON.fromJson(latencyStatsJsonElement, LatencyStats.class);
    JsonElement latencyDataJsonElement = latencyStatsJsonElement.getAsJsonObject().get("data");
    JsonObject latencyDataJsonObject = latencyDataJsonElement.getAsJsonObject();
    Map portLatencyDataMap = new HashMap<>();
    // parse LatencyPortData manually
    for (Map.Entry entry : latencyDataJsonObject.entrySet()) {
      String jsonKey = entry.getKey();
      if (jsonKey.startsWith("port")) {
        Integer portIndex = Integer.parseInt(jsonKey.substring(5));
        LatencyPortData latencyPortData = GSON.fromJson(entry.getValue(), LatencyPortData.class);
        portLatencyDataMap.put(portIndex, latencyPortData);
      }
    }
    latencyStats.getData().setPortLatencyDataMap(portLatencyDataMap);
    return latencyStats;
  }

  /**
   * Get Version
   *
   * @return version
   */
  public String getVersion() {
    Map payload = this.createPayload();
    String json = callMethod("get_version", payload);
    try {
      return getResultFromResponse(json).getAsJsonObject().get("version").getAsString();
    } catch (NullPointerException e) {
      throw new IllegalStateException("could not parse version", e);
    }
  }

  /**
   * get template group names
   *
   * @return template group names
   */
  public List getTemplateGroupNames() {
    return this.getTemplateGroupNames("");
  }

  /**
   * get template group names
   *
   * @param profileId
   * @return template group names
   */
  public List getTemplateGroupNames(String profileId) {
    if (profileId == null || !getProfileIds().contains(profileId)) {
      LOGGER.warn(
          "can not fetch template group names due to invalid profileId, or relative profile is not loaded yet.");
      return Collections.emptyList();
    }

    Map payload = createPayload(profileId);
    payload.put("initialized", false);
    String json = callMethod("get_tg_names", payload);
    JsonArray names =
        getResultFromResponse(json).getAsJsonObject().get("tg_names").getAsJsonArray();
    return StreamSupport.stream(names.spliterator(), false)
        .map(JsonElement::getAsString)
        .collect(Collectors.toList());
  }

  /**
   * get template group statistics
   *
   * @param tgNames
   * @return group statistics
   */
  public Map getTemplateGroupStatistics(List tgNames) {
    return getTemplateGroupStatistics("", tgNames);
  }

  /**
   * get template group statistics
   *
   * @param profileId
   * @param tgNames
   * @return Map key:tgName, value:AstfStatistics
   */
  public Map getTemplateGroupStatistics(
      String profileId, List tgNames) {

    // remove duplicated tgNames in input list
    List tgNames2 = new ArrayList<>(new HashSet<>(tgNames));
    Map stats = new LinkedHashMap<>(tgNames2.size());

    Map payload = createPayload(profileId);
    payload.put("epoch", 1);
    Map name2Id = translateNames2Ids(profileId, tgNames2);
    payload.put("tg_ids", new ArrayList<>(name2Id.values()));

    String json = callMethod("get_tg_id_stats", payload);
    JsonObject result = getResultFromResponse(json).getAsJsonObject();
    MetaData metaData = getAstfStatsMetaData();
    name2Id.forEach(
        (tgName, tgId) -> {
          if (result.get(tgId.toString()) == null) {
            return;
          }
          try {
            AstfStatistics astfStatistics =
                new ObjectMapper()
                    .readValue(result.get(tgId.toString()).toString(), AstfStatistics.class);
            astfStatistics.setCounterNames(metaData);
            stats.put(tgName, astfStatistics);
          } catch (IOException e) {
            LOGGER.error("Error occurred during processing output of get_tg_id_stats method", e);
          }
        });

    return stats;
  }

  /**
   * get template group statistics
   *
   * @return Map key:tgName, value:AstfStatistics
   */
  public ServerStatus syncWithServer() {
    Map payload = createPayload("*");
    return callMethod("sync", payload, ServerStatus.class).get();
  }

  /**
   * translate template group names to ids getTemplateGroupNames with return all template group
   * names, this method will map an id for each name, the id is an increasing integer starting at 1.
   * and filter names by input name list
   *
   * @param profileId
   * @param tgNames
   * @return Map key:tgName, value:tgId
   */
  private Map translateNames2Ids(String profileId, List tgNames) {
    Map name2Id = new LinkedHashMap<>(tgNames.size());
    List allTgNames = getTemplateGroupNames(profileId);
    for (int i = 0; i < allTgNames.size(); i++) {
      if (tgNames.contains(allTgNames.get(i))) {
        name2Id.put(allTgNames.get(i), i + 1);
      }
    }

    return name2Id;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy