shared_ptr ThriftConfigApplier::updatePort()

in fboss/agent/ApplyThriftConfig.cpp [1206:1477]


shared_ptr<Port> ThriftConfigApplier::updatePort(
    const shared_ptr<Port>& orig,
    const cfg::Port* portConf,
    const shared_ptr<Transceiver>& transceiver) {
  CHECK_EQ(orig->getID(), PortID(*portConf->logicalID()));

  auto vlans = portVlans_[orig->getID()];

  std::vector<cfg::PortQueue> cfgPortQueues;
  if (auto portQueueConfigName = portConf->portQueueConfigName()) {
    auto it = cfg_->portQueueConfigs()->find(*portQueueConfigName);
    if (it == cfg_->portQueueConfigs()->end()) {
      throw FbossError(
          "Port queue config name: ",
          *portQueueConfigName,
          " does not exist in PortQueueConfig map");
    }
    cfgPortQueues = it->second;
  }

  const auto& oldIngressMirror = orig->getIngressMirror();
  const auto& oldEgressMirror = orig->getEgressMirror();
  auto newIngressMirror = std::optional<std::string>();
  auto newEgressMirror = std::optional<std::string>();
  if (auto ingressMirror = portConf->ingressMirror()) {
    newIngressMirror = *ingressMirror;
  }
  if (auto egressMirror = portConf->egressMirror()) {
    newEgressMirror = *egressMirror;
  }
  bool mirrorsUnChanged = (oldIngressMirror == newIngressMirror) &&
      (oldEgressMirror == newEgressMirror);

  auto newQosPolicy = std::optional<std::string>();
  if (auto dataPlaneTrafficPolicy = cfg_->dataPlaneTrafficPolicy()) {
    if (auto defaultDataPlaneQosPolicy =
            dataPlaneTrafficPolicy->defaultQosPolicy()) {
      newQosPolicy = *defaultDataPlaneQosPolicy;
    }
    if (auto portIdToQosPolicy = dataPlaneTrafficPolicy->portIdToQosPolicy()) {
      auto qosPolicyItr = portIdToQosPolicy->find(*portConf->logicalID());
      if (qosPolicyItr != portIdToQosPolicy->end()) {
        newQosPolicy = qosPolicyItr->second;
      }
    }
  }

  std::optional<cfg::QosMap> qosMap;
  if (newQosPolicy) {
    bool qosPolicyFound = false;
    for (auto qosPolicy : *cfg_->qosPolicies()) {
      if (qosPolicyFound) {
        break;
      }
      qosPolicyFound = (*qosPolicy.name() == newQosPolicy.value());
      if (qosPolicyFound && qosPolicy.qosMap()) {
        qosMap = qosPolicy.qosMap().value();
      }
    }
    if (!qosPolicyFound) {
      throw FbossError("qos policy ", newQosPolicy.value(), " not found");
    }
  }

  // For now, we only support update unicast port queues for ports
  QueueConfig portQueues;
  for (auto streamType : platform_->getAsic()->getQueueStreamTypes(false)) {
    auto maxQueues =
        platform_->getAsic()->getDefaultNumPortQueues(streamType, false);
    auto tmpPortQueues = updatePortQueues(
        orig->getPortQueues(), cfgPortQueues, maxQueues, streamType, qosMap);
    portQueues.insert(
        portQueues.begin(), tmpPortQueues.begin(), tmpPortQueues.end());
  }
  bool queuesUnchanged = portQueues.size() == orig->getPortQueues().size();
  for (int i = 0; i < portQueues.size() && queuesUnchanged; i++) {
    if (*(portQueues.at(i)) != *(orig->getPortQueues().at(i))) {
      queuesUnchanged = false;
      break;
    }
  }

  auto newSampleDest = std::optional<cfg::SampleDestination>();
  if (portConf->sampleDest()) {
    newSampleDest = portConf->sampleDest().value();

    if (newSampleDest.value() == cfg::SampleDestination::MIRROR &&
        *portConf->sFlowEgressRate() > 0) {
      throw FbossError(
          "Port ",
          orig->getID(),
          ": Egress sampling to mirror destination is unsupported");
    }
  }

  auto newPfc = std::optional<cfg::PortPfc>();
  auto newPfcPriorities = std::optional<std::vector<PfcPriority>>();
  std::optional<PortPgConfigs> portPgCfgs;
  // lets compare the portPgConfigs
  bool portPgConfigUnchanged = true;
  if (portConf->pfc().has_value()) {
    newPfc = portConf->pfc().value();

    auto pause = portConf->pause().value();
    bool pfc_rx = *newPfc->rx();
    bool pfc_tx = *newPfc->tx();
    bool pause_rx = *pause.rx();
    bool pause_tx = *pause.tx();

    if (pfc_rx || pfc_tx) {
      if (!platform_->getAsic()->isSupported(HwAsic::Feature::PFC)) {
        throw FbossError(
            "Port ",
            orig->getID(),
            " has PFC enabled, but its not supported feature for this platform");
      }
      if (pause_rx || pause_tx) {
        throw FbossError(
            "Port ",
            orig->getID(),
            " PAUSE and PFC cannot be enabled on the same port");
      }
    }

    auto portPgConfigName = newPfc->portPgConfigName();
    if (newPfc->watchdog().has_value() && (*portPgConfigName).empty()) {
      throw FbossError(
          "Port ",
          orig->getID(),
          " Priority group must be associated with port "
          "when PFC watchdog is configured");
    }
    if (auto portPgConfigs = cfg_->portPgConfigs()) {
      auto it = portPgConfigs->find(*portPgConfigName);
      if (it == portPgConfigs->end()) {
        throw FbossError(
            "Port ",
            orig->getID(),
            " pg name",
            *portPgConfigName,
            "does not exist in portPgConfig map");
      }
      portPgCfgs = updatePortPgConfigs(it->second, orig);
      // validate that the given pg profile points to valid
      // buffer pool
      validateUpdatePgBufferPoolName(
          portPgCfgs.value(), orig, *portPgConfigName);

      /*
       * Keep track of enabled pfcPriorities which are 1:1
       * mapped to PG id.
       */
      newPfcPriorities = findEnabledPfcPriorities(portPgCfgs.value());
    } else if (!(*portPgConfigName).empty()) {
      throw FbossError(
          "Port: ",
          orig->getID(),
          " pg name: ",
          *portPgConfigName,
          " exist but not the portPgConfig map");
    }
  }
  portPgConfigUnchanged = isPgConfigUnchanged(portPgCfgs, orig);
  /*
   * The list of lookup classes would be different when first enabling the
   * feature and if we had to change the number of lookup classes (unlikely)
   * or disable the queue-per-host feature (for emergency).
   *
   * To avoid unncessary shuffling when only the order of lookup classes
   * changes, (e.g. due to config change), sort and compare. This requires a
   * deep copy and sorting, but in practice, the list of lookup classes would
   * be small (< 10).
   */
  auto origLookupClasses{orig->getLookupClassesToDistributeTrafficOn()};
  auto newLookupClasses{*portConf->lookupClasses()};
  sort(origLookupClasses.begin(), origLookupClasses.end());
  sort(newLookupClasses.begin(), newLookupClasses.end());
  auto lookupClassesUnchanged = (origLookupClasses == newLookupClasses);

  // Now use TransceiverMap as the source of truth to build matcher
  // Prepare the new profileConfig
  std::optional<cfg::PlatformPortConfigOverrideFactor> factor;
  if (transceiver != nullptr) {
    factor = transceiver->toPlatformPortConfigOverrideFactor();
  }
  platform_->getPlatformMapping()->customizePlatformPortConfigOverrideFactor(
      factor);
  PlatformPortProfileConfigMatcher matcher{
      *portConf->profileID(), orig->getID(), factor};

  auto portProfileCfg = platform_->getPortProfileConfig(matcher);
  if (!portProfileCfg) {
    throw FbossError(
        "No port profile config found with matcher:", matcher.toString());
  }
  if (*portConf->state() == cfg::PortState::ENABLED &&
      *portProfileCfg->speed() != *portConf->speed()) {
    throw FbossError(
        orig->getName(),
        " has mismatched speed on profile:",
        apache::thrift::util::enumNameSafe(*portConf->profileID()),
        " and config:",
        apache::thrift::util::enumNameSafe(*portConf->speed()));
  }
  auto newProfileConfigRef = portProfileCfg->iphy();
  auto profileConfigUnchanged =
      (*newProfileConfigRef == orig->getProfileConfig());

  const auto& newPinConfigs =
      platform_->getPlatformMapping()->getPortIphyPinConfigs(matcher);
  auto pinConfigsUnchanged = (newPinConfigs == orig->getPinConfigs());

  XLOG_IF(DBG2, !profileConfigUnchanged || !pinConfigsUnchanged)
      << orig->getName() << " has profileConfig: "
      << (profileConfigUnchanged ? "UNCHANGED" : "CHANGED")
      << ", pinConfigs: " << (pinConfigsUnchanged ? "UNCHANGED" : "CHANGED")
      << ", with matcher:" << matcher.toString();

  // Ensure portConf has actually changed, before applying
  if (*portConf->state() == orig->getAdminState() &&
      VlanID(*portConf->ingressVlan()) == orig->getIngressVlan() &&
      *portConf->speed() == orig->getSpeed() &&
      *portConf->profileID() == orig->getProfileID() &&
      *portConf->pause() == orig->getPause() && newPfc == orig->getPfc() &&
      newPfcPriorities == orig->getPfcPriorities() &&
      *portConf->sFlowIngressRate() == orig->getSflowIngressRate() &&
      *portConf->sFlowEgressRate() == orig->getSflowEgressRate() &&
      newSampleDest == orig->getSampleDestination() &&
      portConf->name().value_or({}) == orig->getName() &&
      portConf->description().value_or({}) == orig->getDescription() &&
      vlans == orig->getVlans() && queuesUnchanged && portPgConfigUnchanged &&
      *portConf->loopbackMode() == orig->getLoopbackMode() &&
      mirrorsUnChanged && newQosPolicy == orig->getQosPolicy() &&
      *portConf->expectedLLDPValues() == orig->getLLDPValidations() &&
      *portConf->maxFrameSize() == orig->getMaxFrameSize() &&
      lookupClassesUnchanged && profileConfigUnchanged && pinConfigsUnchanged) {
    return nullptr;
  }

  auto newPort = orig->clone();

  auto lldpmap = newPort->getLLDPValidations();
  for (const auto& tag : *portConf->expectedLLDPValues()) {
    lldpmap[tag.first] = tag.second;
  }

  newPort->setAdminState(*portConf->state());
  newPort->setIngressVlan(VlanID(*portConf->ingressVlan()));
  newPort->setVlans(vlans);
  newPort->setSpeed(*portConf->speed());
  newPort->setProfileId(*portConf->profileID());
  newPort->setPause(*portConf->pause());
  newPort->setSflowIngressRate(*portConf->sFlowIngressRate());
  newPort->setSflowEgressRate(*portConf->sFlowEgressRate());
  newPort->setSampleDestination(newSampleDest);
  newPort->setName(portConf->name().value_or({}));
  newPort->setDescription(portConf->description().value_or({}));
  newPort->setLoopbackMode(*portConf->loopbackMode());
  newPort->resetPortQueues(portQueues);
  newPort->setIngressMirror(newIngressMirror);
  newPort->setEgressMirror(newEgressMirror);
  newPort->setQosPolicy(newQosPolicy);
  newPort->setExpectedLLDPValues(lldpmap);
  newPort->setLookupClassesToDistributeTrafficOn(*portConf->lookupClasses());
  newPort->setMaxFrameSize(*portConf->maxFrameSize());
  newPort->setPfc(newPfc);
  newPort->setPfcPriorities(newPfcPriorities);
  newPort->resetPgConfigs(portPgCfgs);
  newPort->setProfileConfig(*newProfileConfigRef);
  newPort->resetPinConfigs(newPinConfigs);
  return newPort;
}