pair PlacementAndLoadBalancing::InternalUpdateService()

in src/prod/src/Reliability/LoadBalancing/PlacementAndLoadBalancing.cpp [1980:2677]


pair<ErrorCode, bool> PlacementAndLoadBalancing::InternalUpdateService(ServiceDescription && serviceDescription, bool forceUpdate, bool traceDetail, bool updateMetricConnections, bool skipServicePackageUpdate)
{
    auto itServiceIdMap = serviceToIdMap_.find(serviceDescription.Name);
    if (serviceDescription.ServiceId == 0 && itServiceIdMap == serviceToIdMap_.end())
    {
        // This is a new service, we need to assign next service ID to it.
        serviceDescription.ServiceId = nextServiceId_++;
        serviceToIdMap_.insert(make_pair(serviceDescription.Name, serviceDescription.ServiceId));
        plbStatistics_.AddService(serviceDescription);
    }
    else
    {
        if (itServiceIdMap == serviceToIdMap_.end())
        {
            return make_pair(ErrorCodeValue::ServiceNotFound, false);
        }

        // Service already exists, reuse the existing service ID.
        serviceDescription.ServiceId = itServiceIdMap->second;
    }

    Uint64UnorderedMap<Application>::iterator appIt = applicationTable_.end();
    bool appHasScaleoutOrCapacity = false;
    if (serviceDescription.ApplicationName != L"")
    {
        auto appIter = applicationToIdMap_.find(serviceDescription.ApplicationName);
        if (appIter == applicationToIdMap_.end())
        {
            // In case when service gets created before the application (possible for application created before v4.5)
            // we need to keep track of this application as well, so we assign new AppID to it.
            appIter = applicationToIdMap_.insert(make_pair(serviceDescription.ApplicationName, nextApplicationId_++)).first;
            ApplicationDescription appDescription(move(wstring(serviceDescription.ApplicationName)));
            appDescription.ApplicationId = appIter->second;
            appIt = applicationTable_.insert(make_pair(appDescription.ApplicationId, Application(move(appDescription)))).first;
            if (applicationTable_.size() == 1)
            {
                nextApplicationToBeTraced_ = applicationTable_.begin()->first;
                for (auto it = serviceDomainTable_.begin(); it != serviceDomainTable_.end(); it++)
                {
                    it->second.LastApplicationTraced = applicationTable_.begin()->first;
                }
            }
        }
        else
        {
            // If application already exists, we just need to find it.
            appIt = applicationTable_.find(appIter->second);

            // As we never remove applicaiton from applicationToIdMap_ there is a possibility that we already deleted the application
            // In this case we should not allow service creation
            if (appIt == applicationTable_.end())
            {
                Trace.ApplicationNotFound(serviceDescription.ApplicationName, serviceDescription.ApplicationId, serviceDescription.Name);
                return make_pair(ErrorCodeValue::ApplicationInstanceDeleted, false);
            }

            appHasScaleoutOrCapacity = appIt->second.ApplicationDesc.HasScaleoutOrCapacity();
        }
        serviceDescription.ApplicationId = appIter->second;

    }
    auto itServicePackage = servicePackageTable_.end();
    auto itServicePackageId = servicePackageToIdMap_.find(serviceDescription.ServicePackageIdentifier);
    if (itServicePackageId != servicePackageToIdMap_.end())
    {
        itServicePackage = servicePackageTable_.find(itServicePackageId->second);
        serviceDescription.ServicePackageId = itServicePackageId->second;
        //in case we skip this it means that the service package descritpion was already adjusted with appropriate rg metrics
        if (!skipServicePackageUpdate)
        {
            if (itServicePackage != servicePackageTable_.end())
            {
                PLBConfig const& config = PLBConfig::GetConfig();
                bool isSingleReplicaPerHost = serviceDescription.ServicePackageActivationMode == ServiceModel::ServicePackageActivationMode::Enum::ExclusiveProcess;
                // Always add default metrics.
                serviceDescription.AddDefaultMetrics();
                // Resources should be added as metrics in case that SP is governed.
                for (auto & rgResource : itServicePackage->second.Description.CorrectedRequiredResources)
                {
                    double rgMetricWeight = (rgResource.first == ServiceModel::Constants::SystemMetricNameCpuCores) ?
                        config.CpuCoresMetricWeight :
                        config.MemoryInMBMetricWeight;

                    if (isSingleReplicaPerHost)
                    {
                        serviceDescription.AddRGMetric(rgResource.first, rgMetricWeight, rgResource.second, rgResource.second);
                    }
                    else
                    {
                        serviceDescription.AddRGMetric(rgResource.first, rgMetricWeight, 0, 0);
                    }
                }
            }
        }
    }
    //this is a service package created before 5.6 and we do not have info about it so we will create a new one so that we can keep track of all the services it has
    //so we can do proper safety check when this service package starts using rg
    else
    {
        if (appIt != applicationTable_.end())
        {
            ServicePackageDescription description(
                ServiceModel::ServicePackageIdentifier(serviceDescription.ServicePackageIdentifier),
                std::map<std::wstring, double>(),
                std::vector<std::wstring>());

            auto servicePackageId = nextServicePackageIndex_;
            serviceDescription.ServicePackageId = servicePackageId;
            servicePackageToIdMap_.insert(make_pair(description.ServicePackageIdentifier, nextServicePackageIndex_++));
            itServicePackage = servicePackageTable_.insert(make_pair(servicePackageId, ServicePackage(move(description)))).first;
            appIt->second.AddServicePackage(itServicePackage->second.Description);
            plbStatistics_.AddServicePackage(itServicePackage->second.Description);
            Trace.UpdateServicePackage(itServicePackage->second.Description.Name, itServicePackage->second.Description);
        }
    }

    // This will add default metrics in case that service does not report any other metric.
    // In case that service uses RG, default metrics were added above.
    serviceDescription.AddDefaultMetrics();

    // If service is stateful singleton replica, align primary and secondary default loads
    // This is needed to avoid issues during singleton replica movement,
    // as first new secondary is created, and then promoted to primary
    if (serviceDescription.IsStateful && serviceDescription.TargetReplicaSetSize == 1)
    {
        serviceDescription.AlignSinletonReplicaDefLoads();
    }

    //is this a new service create, or an update for an existing service...
    bool isNewService = serviceToDomainTable_.find(serviceDescription.ServiceId) == serviceToDomainTable_.end();

    //Affinty relationship is not allowed to have chains...
    if (IsAffinityChain(serviceDescription))
    {
        Trace.AffinityChainDetected(serviceDescription.Name, serviceDescription.AffinitizedService);
        if (forceUpdate)
        {
            // Report health error on this service
            plbDiagnosticsSPtr_->ReportServiceCorrelationError(serviceDescription);
            // Remove affinity from service description and proceed
            serviceDescription.ClearAffinitizedService();
        }
        else
        {
            return make_pair(ErrorCodeValue::ServiceAffinityChainNotSupported, false);
        }
    }

    if (!forceUpdate)
    {
        if (!CheckClusterResource(serviceDescription))
        {
            return make_pair(ErrorCodeValue::InsufficientClusterCapacity, false);
        }

        if (PLBConfig::GetConfig().ValidatePlacementConstraint && !IsMaster && !serviceDescription.PlacementConstraints.empty())
        {
            if (!CheckConstraintKeyDefined(serviceDescription))
            {
                Trace.ConstraintKeyUndefined(serviceDescription.Name, serviceDescription.PlacementConstraints);
                return make_pair(ErrorCodeValue::ConstraintKeyUndefined, false);
            }
            else
            {
                if (serviceNameToPlacementConstraintTable_.size() < PLBConfig::GetConfig().PlacementConstraintValidationCacheSize)
                {
                    //Adds this to the table only if the CreateServiceCall it will be successful
                    serviceNameToPlacementConstraintTable_.insert(std::make_pair(serviceDescription.Name, expressionCache_));
                }
                expressionCache_ = nullptr;
            }
        }
    }


    PLBConfig & config = PLBConfig::GetConfig();
    // we take a copy of the metrics instead of reference since
    // we can use it later for reattaching FT's in case the service is being redefined.
    vector<ServiceMetric> metrics = serviceDescription.Metrics;
    ASSERT_IF(metrics.empty(), "Service metrics should not be empty");
    //store a copy of existing failoverUnits and original metrics
    vector<FailoverUnit> relatedFailoverUnits;
    vector<ServiceMetric> originalMetrics;
    //domains to be merged: we only use this when we aren't supposed to update metric connections...(i.e during domain split)
    set<ServiceDomain::DomainId> domains;
    bool areRelatedFailoverUnitsStateful = false;
    //freshly affinitized as child, but parent hasn't been created yet.
    bool isParentAbsent = false;

    // This is used for scaling based on number of partitions
    Common::StopwatchTime serviceScalingExpiry = Common::StopwatchTime::Zero;
    int partitionCountChange = 0;

    auto isAutoScalingPolicyValid = [](ServiceDescription const& serviceDescription)
    {
        int minCount = 0;
        auto autoScalingPolicy = serviceDescription.AutoScalingPolicy;
        if (autoScalingPolicy.Mechanism->Kind == ScalingMechanismKind::PartitionInstanceCount)
        {
            InstanceCountScalingMechanismSPtr mechanism = static_pointer_cast<InstanceCountScalingMechanism>(serviceDescription.AutoScalingPolicy.Mechanism);
            minCount = mechanism->MinimumInstanceCount;
        }
        if (autoScalingPolicy.Mechanism->Kind == ScalingMechanismKind::AddRemoveIncrementalNamedPartition)
        {
            AddRemoveIncrementalNamedPartitionScalingMechanismSPtr mechanism = static_pointer_cast<AddRemoveIncrementalNamedPartitionScalingMechanism>(serviceDescription.AutoScalingPolicy.Mechanism);
            minCount = mechanism->MinimumPartitionCount;
        }
        if (minCount == 0)
        {
            return false;
        }
        else
        {
            return true;
        }
    };

    if (isNewService)
    {
        if (!forceUpdate && serviceDescription.IsAutoScalingDefined)
        {
            //New auto scaling policy should be created with new service, so checking is mincount > 0
            if (!isAutoScalingPolicyValid(serviceDescription))
            {
                Trace.InvalidAutoScaleMinCount(serviceDescription.Name);
                return make_pair(ErrorCodeValue::InvalidServiceScalingPolicy, false);
            }
        }
    }

    bool isScalingPolicyChanged = false;

    //In case of redefinition, we will execute a deleteService() to clear all info. This deleteservice won't split any domains though.
    if (!isNewService)
    {
        ASSERT_IF(!updateMetricConnections, "Metric Connections should always be updated for a non-new service");
        //Get all failover unit associated with that service
        auto itServiceToDomain = serviceToDomainTable_.find(serviceDescription.ServiceId);
        // No need to check itServiceToDomain, because isNewService is true if element is not found
        auto itServiceDomain = serviceDomainTable_.find((itServiceToDomain->second)->first);
        if (itServiceDomain != serviceDomainTable_.end())
        {
            // store original metrics related in original service description
            auto itService = itServiceDomain->second.Services.find(serviceDescription.ServiceId);
            if (itService != itServiceDomain->second.Services.end())
            {
                serviceScalingExpiry = itService->second.NextAutoScaleCheck;
                ServiceDescription const& originalServiceDescription = itService->second.ServiceDesc;
                partitionCountChange = serviceDescription.PartitionCount - originalServiceDescription.PartitionCount;
                originalMetrics = originalServiceDescription.Metrics;

                plbStatistics_.UpdateService(originalServiceDescription, serviceDescription);

                // Check if FTs will have to be checked and updated for AS
                if (serviceDescription.IsAutoScalingDefined != originalServiceDescription.IsAutoScalingDefined)
                {
                    isScalingPolicyChanged = true;
                }
                else if (serviceDescription.IsAutoScalingDefined)
                {
                    isScalingPolicyChanged = serviceDescription.AutoScalingPolicy != originalServiceDescription.AutoScalingPolicy;
                }

                if (!forceUpdate && serviceDescription.IsAutoScalingDefined && isScalingPolicyChanged)
                {
                    // Autoscaling policy is changed so check is it still valid
                    if (!isAutoScalingPolicyValid(serviceDescription))
                    {
                        Trace.InvalidAutoScaleMinCount(serviceDescription.Name);
                        return make_pair(ErrorCodeValue::InvalidServiceScalingPolicy, false);
                    }
                }
                //inquires whether the affinity relationship was changed

                areRelatedFailoverUnitsStateful = originalServiceDescription.IsStateful;
            }

            GuidUnorderedMap<FailoverUnit> const& failoverUnits = (itServiceDomain->second).FailoverUnits;

            for (auto iter = failoverUnits.begin(); iter != failoverUnits.end(); ++iter)
            {
                if (iter->second.FuDescription.ServiceName == serviceDescription.Name)
                {
                    relatedFailoverUnits.push_back(iter->second);
                }
            }
            for (auto iter = relatedFailoverUnits.begin(); iter != relatedFailoverUnits.end(); ++iter)
            {
                wstring serviceName = serviceDescription.Name;
                // Delete failover unit by id and name
                FailoverUnitDescription fuDescription(iter->FUId, move(serviceName), serviceDescription.ServiceId);
                itServiceDomain->second.UpdateFailoverUnit(move(fuDescription), Common::Stopwatch::Now(), false);
            }
        }

        if (!forceUpdate)
        {
            // Error is reported when affinity chain is detected and service update is required to remove affinity chain.
            // Clear the health error only if this is user-initiated service update (!force and !new)
            plbDiagnosticsSPtr_->ReportServiceDescriptionOK(serviceDescription);
        }
        bool deleted = InternalDeleteService(serviceDescription.Name, false, false);
        ASSERT_IFNOT(deleted, "Failed to delete a service for service update.");
    }

    //in case its new service, just make sure its not being re-created due to a splitdomain.
    //if it is being created due to a splitdomain, we will take care of metric merges later.
    if (updateMetricConnections)
    {
        metricConnections_.AddOrRemoveMetricConnectionsForService(metrics, true);
    }

    //add this service to the service package
    if (itServicePackage != servicePackageTable_.end())
    {
        itServicePackage->second.Services.insert(serviceDescription.ServiceId);
    }
    //in case service is part of an application with capacity
    if (!serviceDescription.ApplicationName.empty())
    {
        appIt->second.AddService(serviceDescription.Name);
        if (appHasScaleoutOrCapacity)
        {
            if (updateMetricConnections)
            {
                ChangeServiceMetricsForApplication(
                    serviceDescription.ApplicationId, serviceDescription.Name, metrics);
            }
            else
            {
                //in case the application already has a Domain...
                auto appDomainIt = applicationToDomainTable_.find(serviceDescription.ApplicationId);
                if (appDomainIt != applicationToDomainTable_.end())
                {
                    domains.insert(appDomainIt->second->second.Id);
                }
                else
                    //in case application metrics can connect Domains...
                {
                    auto itAppById = applicationTable_.find(serviceDescription.ApplicationId);
                    DBGASSERT_IF(itAppById == applicationTable_.end(), "Undefined Application {0}", serviceDescription.ApplicationName);
                    if (itAppById != applicationTable_.end() && itAppById->second.ApplicationDesc.HasScaleoutOrCapacity())
                    {
                        auto capacities = itAppById->second.ApplicationDesc.AppCapacities;
                        for (auto capacityIt = capacities.begin(); capacityIt != capacities.end(); capacityIt++)
                        {
                            auto metricDomainIt = metricToDomainTable_.find(capacityIt->second.MetricName);
                            if (metricDomainIt != metricToDomainTable_.end())
                            {
                                domains.insert(metricDomainIt->second->second.Id);
                            }
                        }
                    }
                }
            }
        }
    }
    //in case service was affinitized as child...
    if (!serviceDescription.AffinitizedService.empty())
    {
        auto const& itId = serviceToIdMap_.find(serviceDescription.AffinitizedService);
        if (itId != serviceToIdMap_.end())
        {
            auto itDomain = serviceToDomainTable_.find(itId->second);
            if (itDomain != serviceToDomainTable_.end())
            {
                auto itService = itDomain->second->second.Services.find(itId->second);
                if (itService != itDomain->second->second.Services.end())
                {
                    if (isNewService && itService->second.ServiceDesc.PartitionCount > 1)
                    {
                        Trace.ParentServiceMultipleParitions(itService->second.ServiceDesc);
                    }
                }

                //make sure its not being re-created due to a splitdomain
                if (updateMetricConnections)
                {
                    auto itSecondService = itDomain->second->second.Services.find(itId->second);
                    if (itSecondService != itDomain->second->second.Services.end())
                    {
                        metricConnections_.AddOrRemoveMetricAffinity(metrics, itSecondService->second.ServiceDesc.Metrics, true);
                    }
                }
                else
                {
                    domains.insert(itDomain->second->second.Id);
                }
            }
            else
            {
                isParentAbsent = true;
            }
        }
        else
        {
            isParentAbsent = true;
        }
    }
    //in case it's affinitized as parent
    else
    {
        bool hasChildren = false;
        auto it = dependedServiceToDomainTable_.find(serviceDescription.Name);
        //parent created after child...
        if (it != dependedServiceToDomainTable_.end())
        {
            DBGASSERT_IF(it->second.empty(), "dependedServiceToDomainTable entry shouldn't contain 0 domains for service {0}", serviceDescription.Name);
            vector<wstring> dependentDomains;
            dependentDomains.reserve(it->second.size());
            for (auto domainIt = it->second.begin(); domainIt != it->second.end(); ++domainIt)
            {
                dependentDomains.push_back((*domainIt)->second.Id);
                auto childServices = (*domainIt)->second.ChildServices.find(serviceDescription.Name);
                if (childServices != (*domainIt)->second.ChildServices.end())
                {
                    for (auto itChild = childServices->second.begin(); itChild != childServices->second.end(); itChild++)
                    {
                        uint64 childServiceId = GetServiceId(*itChild);

                        if (childServiceId != 0)
                        {
                            auto itService = (*domainIt)->second.Services.find(childServiceId);
                            hasChildren = true;

                            if (updateMetricConnections)
                            {
                                auto itSecondService = (*domainIt)->second.Services.find(childServiceId);
                                if (itSecondService != (*domainIt)->second.Services.end())
                                {
                                    metricConnections_.AddOrRemoveMetricAffinity(metrics, itSecondService->second.ServiceDesc.Metrics, true);
                                }
                            }
                            else
                            {
                                auto itDomain = serviceToDomainTable_.find(childServiceId);
                                if (itDomain != serviceToDomainTable_.end())
                                {
                                    domains.insert(itDomain->second->second.Id);
                                }
                            }
                        }
                    }
                }
            }
            dependedServiceToDomainTable_.erase(it);
        }

        if (isNewService && hasChildren && serviceDescription.PartitionCount > 1)
        {
            Trace.ParentServiceMultipleParitions(serviceDescription);
        }
    }
    if (updateMetricConnections)
    {
        //If it is a new service, we should not bother with trying to Split Domains...
        ExecuteDomainChange(true);
    }

    for (auto itDomain = serviceDomainTable_.begin(); itDomain != serviceDomainTable_.end(); itDomain++)
    {
        DBGASSERT_IF(!metricConnections_.AreMetricsConnected(itDomain->second.Metrics) && isNewService && PLBConfig::GetConfig().SplitDomainEnabled, "Metrics aren't connected before adding service: {0}", serviceDescription.Name);
    }

    ServiceDomainTable::iterator itNewDomain = serviceDomainTable_.end();
    for (auto itMetric = metrics.begin(); itMetric != metrics.end(); ++itMetric)
    {
        auto itMetricToDomain = metricToDomainTable_.find(itMetric->Name);
        if (itMetricToDomain != metricToDomainTable_.end())
        {
            if (updateMetricConnections)
            {
                DBGASSERT_IF(itNewDomain != itMetricToDomain->second && itNewDomain != serviceDomainTable_.end(), "Domains we not properly merged for {0}", metrics);
                //we decided the domain from its metrics.
                itNewDomain = itMetricToDomain->second;
            }
            else
            {
                domains.insert(itMetricToDomain->second->second.Id);
            }
        }

        if (!itMetric->IsBuiltIn && !itMetric->IsMetricOfDefaultServices() &&
            (config.MetricBalancingThresholds.find(itMetric->Name) == config.MetricBalancingThresholds.end() ||
                config.MetricActivityThresholds.find(itMetric->Name) == config.MetricActivityThresholds.end()))
        {
            // Trace warning that metric does not have activity or balancing threshold
            if (traceDetail)
            {
                Trace.MetricDoesNotHaveThreshold(itMetric->Name, serviceDescription.Name);
            }
        }
    }
    //if we're not performing metric connection operations, we get the domain from it's dependencies.
    if (!domains.empty() && !updateMetricConnections)
    {
        itNewDomain = MergeDomains(domains);
    }
    //if we couldn't find any domain, we will create a new one.
    if (itNewDomain == serviceDomainTable_.end())
    {
        ServiceDomain::DomainId newDomainId = GenerateUniqueDomainId(serviceDescription);
        ServiceDomain newDomain(ServiceDomain::DomainId(newDomainId), *this);

        std::wstring prefix = ServiceDomain::GetDomainIdPrefix(newDomainId);
        itNewDomain = serviceDomainTable_.insert(make_pair(move(prefix), move(newDomain))).first;

        if (traceDetail)
        {
            Trace.AddServiceDomain(itNewDomain->second.Id);
        }
    }
    //Various domain addition operations(app+dependedservice+service)...
    if (appHasScaleoutOrCapacity)
    {
        // No need to check if application exists because it is added above and we're under lock
        AddApplicationToDomain(applicationTable_.find(serviceDescription.ApplicationId)->second.ApplicationDesc, itNewDomain, vector<wstring>());
    }
    //if paren't hasnt been created yet, we add depended service to itNewDomain
    if (isParentAbsent)
    {
        AddDependedServiceToDomain(serviceDescription.AffinitizedService, itNewDomain);
    }
    auto serviceId = serviceDescription.ServiceId;

    if (isScalingPolicyChanged || isNewService)
    {
        if (serviceDescription.IsAutoScalingDefined)
        {
            if (serviceDescription.AutoScalingPolicy.IsServiceScaled())
            {
                serviceScalingExpiry = Stopwatch::Now() + serviceDescription.AutoScalingPolicy.GetScalingInterval();
            }
            else
            {
                serviceScalingExpiry = Common::StopwatchTime::Zero;
            }
        }
        else
        {
            serviceScalingExpiry = Common::StopwatchTime::Zero;
        }
    }

    AddServiceToDomain(move(serviceDescription), itNewDomain);

    AutoScaler & autoScaler = itNewDomain->second.AutoScalerComponent;
    autoScaler.AddService(serviceId, serviceScalingExpiry);
    autoScaler.UpdateServicePartitionCount(serviceId, partitionCountChange);

    auto serviceIt = itNewDomain->second.Services.find(serviceId);
    // Keep this updated
    if (serviceIt != itNewDomain->second.Services.end())
    {
        serviceIt->second.NextAutoScaleCheck = serviceScalingExpiry;
    }

    DBGASSERT_IF(itNewDomain->second.CheckMetrics() && PLBConfig::GetConfig().SplitDomainEnabled, "Found metrics with No services and No Applications in {0}", itNewDomain->second.Id);

    //reattach the failover units...
    if (!isNewService)
    {
        auto itServiceToDomain = serviceToDomainTable_.find(serviceId);
        if (itServiceToDomain != serviceToDomainTable_.end())
        {
            auto & service = itServiceToDomain->second->second.GetService(serviceId);
            // add back failover units when update service
            for (auto iter = relatedFailoverUnits.begin(); iter != relatedFailoverUnits.end(); ++iter)
            {
                FailoverUnit ft = *iter;

                ASSERT_IFNOT(originalMetrics.size() == ft.PrimaryEntries.size() &&
                    originalMetrics.size() == ft.SecondaryEntries.size(),
                    "original metrics size does not match with load entry size in failover unit. MetricsSize:{0}, PrimaryEntry:{1}, SecondaryEntry:{2}",
                    originalMetrics.size(), ft.PrimaryEntries.size(), ft.SecondaryEntries.size());

                vector<uint> primaryEntries;
                vector<uint> secondaryEntries;
                map<Federation::NodeId, std::vector<uint>> newSecondaryEntriesMap;

                for (int j = 0; j < metrics.size(); ++j)
                {
                    primaryEntries.push_back(service.GetDefaultMetricLoad(j, ReplicaRole::Primary));
                    secondaryEntries.push_back(service.GetDefaultMetricLoad(j, ReplicaRole::Secondary));
                }
                for (auto it = ft.SecondaryEntriesMap.begin(); it != ft.SecondaryEntriesMap.end(); ++it)
                {
                    newSecondaryEntriesMap.insert(std::make_pair(it->first, secondaryEntries));
                }

                StopwatchTime scalingExpiry = ft.NextScalingCheck;

                //if the scaling policy has changed we need to recompute the next interval to look at it
                //if we have removed the policy now we should just set the interval to zero
                //else we calculate it from this point + scale interval
                if (isScalingPolicyChanged)
                {
                    if (service.ServiceDesc.IsAutoScalingDefined)
                    {
                        if (service.ServiceDesc.AutoScalingPolicy.IsPartitionScaled())
                        {
                            scalingExpiry = Stopwatch::Now() + service.ServiceDesc.AutoScalingPolicy.GetScalingInterval();
                        }
                        else
                        {
                            scalingExpiry = StopwatchTime::Zero;
                        }
                    }
                    else
                    {
                        scalingExpiry = StopwatchTime::Zero;
                    }
                }

                FailoverUnitDescription newFTDescription(ft.FuDescription);

                //if no scaling policy, or if scaling is not per partition, target is always equal to the one from service description
                if (!service.ServiceDesc.IsAutoScalingDefined || !service.ServiceDesc.AutoScalingPolicy.IsPartitionScaled())
                {
                    newFTDescription.TargetReplicaSetSize = service.ServiceDesc.TargetReplicaSetSize;
                }

                //We create FailoverUnit with all default values for Move and Load costs
                FailoverUnit newFt(move(newFTDescription), move(primaryEntries), move(secondaryEntries),
                    move(newSecondaryEntriesMap), service.GetDefaultMoveCost(ReplicaRole::Primary), service.GetDefaultMoveCost(ReplicaRole::Secondary),
                    serviceDescription.OnEveryNode, move(ft.ResourceLoadMap), scalingExpiry);

                //Recostructing FailoverUnit by updating with original primary and secondary load costs
                for (int j = 0; j < metrics.size(); ++j)
                {
                    wstring const & metricName = metrics[j].Name;
                    for (size_t k = 0; k < originalMetrics.size(); ++k)
                    {
                        // If the metric also defined before and it is not default value (updated by service report load),
                        // we should keep the reported load
                        // otherwise we will still use the new default load.
                        if (originalMetrics[k].Name == metricName)
                        {
                            if (areRelatedFailoverUnitsStateful && ft.IsPrimaryLoadReported[k])
                            {
                                newFt.UpdateLoad(ReplicaRole::Primary, static_cast<size_t>(j), ft.PrimaryEntries[k], this->Settings);
                            }
                            if (!this->Settings.UseSeparateSecondaryLoad)
                            {
                                if (ft.IsSecondaryLoadReported[k])
                                {
                                    newFt.UpdateLoad(ReplicaRole::Secondary, static_cast<size_t>(j), ft.SecondaryEntries[k], this->Settings);
                                }
                            }
                            else
                            {
                                for (auto it = ft.IsSecondaryLoadMapReported.begin(); it != ft.IsSecondaryLoadMapReported.end(); ++it)
                                {
                                    if (it->second[k])
                                    {
                                        auto itMap = ft.SecondaryEntriesMap.find(it->first);
                                        if (itMap != ft.SecondaryEntriesMap.end())
                                        {
                                            newFt.UpdateLoad(ReplicaRole::Secondary, static_cast<size_t>(j), itMap->second[k], this->Settings, true, it->first);
                                        }
                                    }
                                }
                            }
                            break;
                        }
                    }
                }
                // Recostructing FailoverUnit by updating with original primary and secondary move costs
                if (ft.IsMoveCostReported(ReplicaRole::Primary))
                {
                    newFt.UpdateMoveCost(ReplicaRole::Primary, ft.PrimaryMoveCost);
                }

                if (ft.IsMoveCostReported(ReplicaRole::Secondary))
                {
                    newFt.UpdateMoveCost(ReplicaRole::Secondary, ft.SecondaryMoveCost);
                }

                auto itServiceToDomain2 = serviceToDomainTable_.find(newFt.FuDescription.ServiceId);
                // Failover units that were deleted for update are reattached to service here. If service is missing in serviceToDomainTable_
                // assert is neccessary, because PLB would lose track of service failover units completely.
                ASSERT_IF(itServiceToDomain2 == serviceToDomainTable_.end(), "Service {0} doesn't exist in service to domain id table", newFt.FuDescription.ServiceId);
                itServiceToDomain2->second->second.AddFailoverUnit(move(newFt), Stopwatch::Now());
            }
        }
    }
    //Split at the end(we check !isNewService, because new services cannot cause a domain split.)...
    if (updateMetricConnections && !isNewService)
    {
        ExecuteDomainChange();
    }

    for (auto itDomain = serviceDomainTable_.begin(); itDomain != serviceDomainTable_.end(); itDomain++)
    {
        DBGASSERT_IF(!metricConnections_.AreMetricsConnected(itDomain->second.Metrics) && PLBConfig::GetConfig().SplitDomainEnabled, "Metrics aren't connected for: {0}", itDomain->second.Id);
    }
    return make_pair(ErrorCodeValue::Success, false);
}