<commit_before>package com.swisscom.cloud.sb.broker.services.bosh

import com.google.common.annotations.VisibleForTesting
import com.google.common.base.Optional
import com.google.common.base.Preconditions
import com.swisscom.cloud.sb.broker.model.Parameter
import com.swisscom.cloud.sb.broker.model.Plan
import com.swisscom.cloud.sb.broker.model.ProvisionRequest
import com.swisscom.cloud.sb.broker.model.ServiceDetail
import com.swisscom.cloud.sb.broker.provisioning.lastoperation.LastOperationJobContext
import com.swisscom.cloud.sb.broker.services.bosh.client.BoshClient
import com.swisscom.cloud.sb.broker.services.bosh.client.BoshClientFactory
import com.swisscom.cloud.sb.broker.services.bosh.dto.TaskDto
import com.swisscom.cloud.sb.broker.services.common.TemplateConfig
import com.swisscom.cloud.sb.broker.services.mongodb.enterprise.openstack.OpenStackClient
import com.swisscom.cloud.sb.broker.services.mongodb.enterprise.openstack.OpenStackClientFactory
import com.swisscom.cloud.sb.broker.util.Resource
import com.swisscom.cloud.sb.broker.util.servicedetail.ServiceDetailKey
import com.swisscom.cloud.sb.broker.util.servicedetail.ServiceDetailsHelper
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j

@CompileStatic
@Slf4j
class BoshFacade {
    public static final String PLAN_PARAMETER_BOSH_VM_INSTANCE_TYPE = 'vm_instance_type'
    public static final String PLAN_PARAMETER_CREATE_OPEN_STACK_SERVER_GROUP = 'create_openstack_server_group'

    public static final String DEPLOYMENT_PREFIX = 'd-'
    public static final String HOST_NAME_POSTFIX = '.service.consul'
    public static final String PARAM_BOSH_DIRECTOR_UUID = "bosh-director-uuid"
    public static final String PARAM_PREFIX = 'prefix'
    public static final String PARAM_GUID = 'guid'
    public static final String PARAM_VM_TYPE = 'vm-type'


    private final BoshClientFactory boshClientFactory
    private final OpenStackClientFactory openStackClientFactory
    private final BoshBasedServiceConfig serviceConfig
    private final BoshTemplateFactory boshTemplateFactory
    private final TemplateConfig templateConfig

    BoshFacade(BoshClientFactory boshClientFactory, OpenStackClientFactory openStackClientFactory, BoshBasedServiceConfig serviceConfig, BoshTemplateFactory boshTemplateFactory, TemplateConfig templateConfig) {
        this.boshClientFactory = boshClientFactory
        this.openStackClientFactory = openStackClientFactory
        this.serviceConfig = serviceConfig
        this.boshTemplateFactory = boshTemplateFactory
        this.templateConfig = templateConfig
    }

    String createOpenStackServerGroup(String name) {
        return createOpenstackClient().createAntiAffinityServerGroup(name).id
    }

    private OpenStackClient createOpenstackClient() {
        return openStackClientFactory.createOpenStackClient(serviceConfig.openstackkUrl,
                serviceConfig.openstackUsername, serviceConfig.openstackPassword, serviceConfig.openstackTenantName)
    }

    def addOrUpdateVmInBoshCloudConfig(LastOperationJobContext context) {
        createBoshClient().addOrUpdateVmInCloudConfig(context.provisionRequest.serviceInstanceGuid, findBoshVmInstanceType(context.plan), findServerGroupId(context).get())
    }

    @VisibleForTesting
    private Optional<String> findServerGroupId(LastOperationJobContext context) {
        Optional<String> maybeGroupId = ServiceDetailsHelper.from(context.serviceInstance.details).findValue(BoshServiceDetailKey.CLOUD_PROVIDER_SERVER_GROUP_ID)
        if (maybeGroupId.present) {
            return maybeGroupId
        } else {
            def maybeServerGroup = createOpenstackClient().findServerGroup(context.serviceInstance.guid)
            if (maybeServerGroup.present) {
                return Optional.of(maybeServerGroup.get().id)
            } else {
                log.warn("ServerGroup Id not found for serviceInstance:${context.serviceInstance.guid}")
                return Optional.absent()
            }
        }
    }

    private static String findBoshTaskIdForDeploy(LastOperationJobContext context) {
        return ServiceDetailsHelper.from(context.serviceInstance.details).getValue(BoshServiceDetailKey.BOSH_TASK_ID_FOR_DEPLOY)
    }

    private static String findBoshVmInstanceType(Plan plan) {
        def instanceTypeParam = plan.parameters.find { it.name == PLAN_PARAMETER_BOSH_VM_INSTANCE_TYPE }
        if (!instanceTypeParam) {
            throw new RuntimeException("Missing plan paramemter:${PLAN_PARAMETER_BOSH_VM_INSTANCE_TYPE}")
        }
        return instanceTypeParam.value
    }

    Collection<ServiceDetail> handleTemplatingAndCreateDeployment(ProvisionRequest provisionRequest, BoshTemplateCustomizer templateCustomizer) {

        BoshTemplate template = boshTemplateFactory.build(readTemplateContent(provisionRequest.plan.templateUniqueIdentifier))

        if (serviceConfig.shuffleAzs) {
            template.shuffleAzs()
        }

        template.replace(PARAM_GUID, provisionRequest.serviceInstanceGuid)
        template.replace(PARAM_PREFIX, DEPLOYMENT_PREFIX)
        template.replace(PARAM_BOSH_DIRECTOR_UUID, createBoshClient().fetchBoshInfo().uuid)
        template.replace(BoshFacade.PARAM_VM_TYPE, shouldCreateOpenStackServerGroup(provisionRequest.plan)?provisionRequest.serviceInstanceGuid:findBoshVmInstanceType(provisionRequest.plan))

        updateTemplateFromDatabaseConfiguration(template, provisionRequest)

        def serviceDetails = templateCustomizer.customizeBoshTemplate(template, provisionRequest)

        if (!serviceDetails) {
            serviceDetails = []
        }
        serviceDetails.add(ServiceDetail.from(BoshServiceDetailKey.BOSH_DEPLOYMENT_ID, generateDeploymentId(provisionRequest.serviceInstanceGuid)))
        serviceDetails.add(ServiceDetail.from(BoshServiceDetailKey.BOSH_TASK_ID_FOR_DEPLOY, createBoshClient().postDeployment(template.build())))

        generateHostNames(provisionRequest.serviceInstanceGuid, template.instanceCount()).each {
            serviceDetails.add(ServiceDetail.from(ServiceDetailKey.HOST, it))
        }


        return serviceDetails
    }

    @VisibleForTesting
    private List<String> generateHostNames(String guid, int hostCount) {
        (List<String>) (0..<hostCount).inject([]) {
            result, i -> result.add("${guid}-${i}${HOST_NAME_POSTFIX}"); return result
        }
    }

    private String readTemplateContent(String templateIdentifier, String version = "1.0.0") {
        try {
            String template = templateConfig.getTemplateForServiceKey(templateIdentifier, version)[0]
            return template
        } catch (NoSuchElementException e) {
            Preconditions.checkNotNull(templateIdentifier, 'Need a valid template name!')
            String fileName = templateIdentifier + (templateIdentifier.endsWith('.yml') ? '' : '.yml')
            File file = new File(serviceConfig.boshManifestFolder, fileName)
            if (file.exists()) {
                log.info("Using template file:${file.absolutePath}")
                return file.text
            }
            log.info("Will try to read file:${fileName} from embedded resources")
            return Resource.readTestFileContent(fileName.startsWith('/') ? fileName : ('/' + fileName))
        }
    }

    @VisibleForTesting
    private static String generateDeploymentId(String serviceInstanceGuid) {
        return DEPLOYMENT_PREFIX + serviceInstanceGuid
    }

    private
    static void updateTemplateFromDatabaseConfiguration(BoshTemplate template, ProvisionRequest provisionRequest) {
        provisionRequest.plan.parameters?.each { Parameter p -> template.replace(p.name, p.value) }
    }

    boolean isBoshDeployTaskSuccessful(LastOperationJobContext context) {
        return isBoshTaskSuccessful(findBoshTaskIdForDeploy(context))
    }
<commit_msg>Fix precondition check in `readTemplateContent` in BoshFacade<commit_after>package com.swisscom.cloud.sb.broker.services.bosh

import com.google.common.annotations.VisibleForTesting
import com.google.common.base.Optional
import com.google.common.base.Preconditions
import com.swisscom.cloud.sb.broker.model.Parameter
import com.swisscom.cloud.sb.broker.model.Plan
import com.swisscom.cloud.sb.broker.model.ProvisionRequest
import com.swisscom.cloud.sb.broker.model.ServiceDetail
import com.swisscom.cloud.sb.broker.provisioning.lastoperation.LastOperationJobContext
import com.swisscom.cloud.sb.broker.services.bosh.client.BoshClient
import com.swisscom.cloud.sb.broker.services.bosh.client.BoshClientFactory
import com.swisscom.cloud.sb.broker.services.bosh.dto.TaskDto
import com.swisscom.cloud.sb.broker.services.common.TemplateConfig
import com.swisscom.cloud.sb.broker.services.mongodb.enterprise.openstack.OpenStackClient
import com.swisscom.cloud.sb.broker.services.mongodb.enterprise.openstack.OpenStackClientFactory
import com.swisscom.cloud.sb.broker.util.Resource
import com.swisscom.cloud.sb.broker.util.servicedetail.ServiceDetailKey
import com.swisscom.cloud.sb.broker.util.servicedetail.ServiceDetailsHelper
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j

@CompileStatic
@Slf4j
class BoshFacade {
    public static final String PLAN_PARAMETER_BOSH_VM_INSTANCE_TYPE = 'vm_instance_type'
    public static final String PLAN_PARAMETER_CREATE_OPEN_STACK_SERVER_GROUP = 'create_openstack_server_group'

    public static final String DEPLOYMENT_PREFIX = 'd-'
    public static final String HOST_NAME_POSTFIX = '.service.consul'
    public static final String PARAM_BOSH_DIRECTOR_UUID = "bosh-director-uuid"
    public static final String PARAM_PREFIX = 'prefix'
    public static final String PARAM_GUID = 'guid'
    public static final String PARAM_VM_TYPE = 'vm-type'


    private final BoshClientFactory boshClientFactory
    private final OpenStackClientFactory openStackClientFactory
    private final BoshBasedServiceConfig serviceConfig
    private final BoshTemplateFactory boshTemplateFactory
    private final TemplateConfig templateConfig

    BoshFacade(BoshClientFactory boshClientFactory, OpenStackClientFactory openStackClientFactory, BoshBasedServiceConfig serviceConfig, BoshTemplateFactory boshTemplateFactory, TemplateConfig templateConfig) {
        this.boshClientFactory = boshClientFactory
        this.openStackClientFactory = openStackClientFactory
        this.serviceConfig = serviceConfig
        this.boshTemplateFactory = boshTemplateFactory
        this.templateConfig = templateConfig
    }

    String createOpenStackServerGroup(String name) {
        return createOpenstackClient().createAntiAffinityServerGroup(name).id
    }

    private OpenStackClient createOpenstackClient() {
        return openStackClientFactory.createOpenStackClient(serviceConfig.openstackkUrl,
                serviceConfig.openstackUsername, serviceConfig.openstackPassword, serviceConfig.openstackTenantName)
    }

    def addOrUpdateVmInBoshCloudConfig(LastOperationJobContext context) {
        createBoshClient().addOrUpdateVmInCloudConfig(context.provisionRequest.serviceInstanceGuid, findBoshVmInstanceType(context.plan), findServerGroupId(context).get())
    }

    @VisibleForTesting
    private Optional<String> findServerGroupId(LastOperationJobContext context) {
        Optional<String> maybeGroupId = ServiceDetailsHelper.from(context.serviceInstance.details).findValue(BoshServiceDetailKey.CLOUD_PROVIDER_SERVER_GROUP_ID)
        if (maybeGroupId.present) {
            return maybeGroupId
        } else {
            def maybeServerGroup = createOpenstackClient().findServerGroup(context.serviceInstance.guid)
            if (maybeServerGroup.present) {
                return Optional.of(maybeServerGroup.get().id)
            } else {
                log.warn("ServerGroup Id not found for serviceInstance:${context.serviceInstance.guid}")
                return Optional.absent()
            }
        }
    }

    private static String findBoshTaskIdForDeploy(LastOperationJobContext context) {
        return ServiceDetailsHelper.from(context.serviceInstance.details).getValue(BoshServiceDetailKey.BOSH_TASK_ID_FOR_DEPLOY)
    }

    private static String findBoshVmInstanceType(Plan plan) {
        def instanceTypeParam = plan.parameters.find { it.name == PLAN_PARAMETER_BOSH_VM_INSTANCE_TYPE }
        if (!instanceTypeParam) {
            throw new RuntimeException("Missing plan paramemter:${PLAN_PARAMETER_BOSH_VM_INSTANCE_TYPE}")
        }
        return instanceTypeParam.value
    }

    Collection<ServiceDetail> handleTemplatingAndCreateDeployment(ProvisionRequest provisionRequest, BoshTemplateCustomizer templateCustomizer) {

        BoshTemplate template = boshTemplateFactory.build(readTemplateContent(provisionRequest.plan.templateUniqueIdentifier))

        if (serviceConfig.shuffleAzs) {
            template.shuffleAzs()
        }

        template.replace(PARAM_GUID, provisionRequest.serviceInstanceGuid)
        template.replace(PARAM_PREFIX, DEPLOYMENT_PREFIX)
        template.replace(PARAM_BOSH_DIRECTOR_UUID, createBoshClient().fetchBoshInfo().uuid)
        template.replace(BoshFacade.PARAM_VM_TYPE, shouldCreateOpenStackServerGroup(provisionRequest.plan)?provisionRequest.serviceInstanceGuid:findBoshVmInstanceType(provisionRequest.plan))

        updateTemplateFromDatabaseConfiguration(template, provisionRequest)

        def serviceDetails = templateCustomizer.customizeBoshTemplate(template, provisionRequest)

        if (!serviceDetails) {
            serviceDetails = []
        }
        serviceDetails.add(ServiceDetail.from(BoshServiceDetailKey.BOSH_DEPLOYMENT_ID, generateDeploymentId(provisionRequest.serviceInstanceGuid)))
        serviceDetails.add(ServiceDetail.from(BoshServiceDetailKey.BOSH_TASK_ID_FOR_DEPLOY, createBoshClient().postDeployment(template.build())))

        generateHostNames(provisionRequest.serviceInstanceGuid, template.instanceCount()).each {
            serviceDetails.add(ServiceDetail.from(ServiceDetailKey.HOST, it))
        }


        return serviceDetails
    }

    @VisibleForTesting
    private List<String> generateHostNames(String guid, int hostCount) {
        (List<String>) (0..<hostCount).inject([]) {
            result, i -> result.add("${guid}-${i}${HOST_NAME_POSTFIX}"); return result
        }
    }

    private String readTemplateContent(String templateIdentifier, String version = "1.0.0") {
        Preconditions.checkNotNull(templateIdentifier, 'Need a valid template name!')
        try {
            String template = templateConfig.getTemplateForServiceKey(templateIdentifier, version)[0]
            return template
        } catch (NoSuchElementException e) {
            // Fallback method which was used by BOSH deployments
            String fileName = templateIdentifier + (templateIdentifier.endsWith('.yml') ? '' : '.yml')
            File file = new File(serviceConfig.boshManifestFolder, fileName)
            if (file.exists()) {
                log.info("Using template file:${file.absolutePath}")
                return file.text
            }
            log.info("Will try to read file:${fileName} from embedded resources")
            return Resource.readTestFileContent(fileName.startsWith('/') ? fileName : ('/' + fileName))
        }
    }

    @VisibleForTesting
    private static String generateDeploymentId(String serviceInstanceGuid) {
        return DEPLOYMENT_PREFIX + serviceInstanceGuid
    }

    private
    static void updateTemplateFromDatabaseConfiguration(BoshTemplate template, ProvisionRequest provisionRequest) {
        provisionRequest.plan.parameters?.each { Parameter p -> template.replace(p.name, p.value) }
    }

    boolean isBoshDeployTaskSuccessful(LastOperationJobContext context) {
        return isBoshTaskSuccessful(findBoshTaskIdForDeploy(context))
    }

{'fraction_non_alphanumeric': 0.07754483455418035, 'fraction_numerical': 0.0008840616317251831, 'mean_word_length': 4.1924590163934425, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2773533', 'n_tokens_mistral': 4300, 'n_tokens_neox': 4261, 'n_words': 686}