/**
 * Upgrade OpenStack packages on control plane nodes.
 * There are no silver boollet in uprading cloud.
 * Update packages on given nodes
 *
 * Expected parameters:
 *   SALT_MASTER_CREDENTIALS            Credentials to the Salt API.
 *   SALT_MASTER_URL                    Full Salt API address [http://10.10.10.1:8000].
 *   OS_DIST_UPGRADE                    Upgrade system packages including kernel (apt-get dist-upgrade)
 *   OS_UPGRADE                         Upgrade all installed applications (apt-get upgrade)
 *   TARGET_SERVERS                     Comma separated list of salt compound definitions to upgrade.
 *   INTERACTIVE                        Ask interactive questions during pipeline run (bool).
 *
 * TODO:
 *   * Add OS_RELEASE_UPGRADE
**/

def common = new com.mirantis.mk.Common()
def salt = new com.mirantis.mk.Salt()
def python = new com.mirantis.mk.Python()
def debian = new com.mirantis.mk.Debian()
def openstack = new com.mirantis.mk.Openstack()

def interactive = INTERACTIVE.toBoolean()
def LinkedHashMap upgradeStageMap = [:]

upgradeStageMap.put('Pre upgrade',
  [
    'Description': 'Only non destructive actions will be applied during this phase. Basic api, service verification will be performed.',
    'Status': 'NOT_LAUNCHED',
    'Expected behaviors': '''
 * No service downtime
 * No workload downtime''',
    'Launched actions': '''
 * Verify API, perform basic CRUD operations for services.
 * Verify that compute/neutron agents on hosts are up.
 * Run some service built in checkers like keystone-manage doctor or nova-status upgrade.''',
    'State result': 'Basic checks around services API are passed.'
  ])
upgradeStageMap.put('Stop OpenStack services',
  [
    'Description': 'All OpenStack python services will be stopped on All control nodes. This does not affect data plane services such as openvswitch or qemu.',
    'Status': 'NOT_LAUNCHED',
    'Expected behaviors': '''
 * OpenStack python services are stopped.
 * OpenStack API are not accessible from this point.
 * No workload downtime''',
    'Launched actions': '''
 * Stop OpenStack python services''',
    'State result': 'OpenStack python services are stopped',
  ])
upgradeStageMap.put('Upgrade OpenStack',
   [
    'Description': 'OpenStack python code will be upgraded during this stage. No workload downtime is expected.',
    'Status': 'NOT_LAUNCHED',
    'Expected behaviors': '''
 * OpenStack services might flap
 * No workload downtime''',
    'Launched actions': '''
 * Install new version of OpenStack packages
 * Render version of configs
 * Apply offline dbsync
 * Start OpenStack services
 * Verify agents are alive/connected
 * Run basic API validation''',
    'State result': '''
 * OpenStack packages are upgraded
 * Services are running
 * Basic checks around services API are passed
 * Verified that agents/services on data plane nodes are connected to new control plane
'''
  ])
upgradeStageMap.put('Upgrade OS',
  [
    'Description': 'Optional step. OS packages will be upgraded during this phase, depending on the job parameters dist-upgrade might be called. And reboot of node executed.',
    'Status': 'NOT_LAUNCHED',
    'Expected behaviors': '''
 * OpenStack services might flap
 * No workload downtime
 * The nodes might be rebooted''',
    'Launched actions': '''
 * Install new version of system packages
 * If doing dist-upgrade new kernel might be installed and node rebooted
 * Verify agents are alive/connected
 * Run basic API validation''',
    'State result': '''
 * System packages are updated
 * Services are running
 * Basic checks around services API are passed
 * Verified that agents/services on data plane nodes are connected
 * Node might be rebooted
'''
  ])

def stopOpenStackServices(env, target) {
    def salt = new com.mirantis.mk.Salt()
    def openstack = new com.mirantis.mk.Openstack()
    def common = new com.mirantis.mk.Common()

    def services = openstack.getOpenStackUpgradeServices(env, target)
    def st
    for (service in services){
        st = "${service}.upgrade.service_stopped".trim()
        common.infoMsg("Stopping ${st} services on ${target}")
        salt.enforceState(env, target, st)
    }
}

def snapshotVM(env, domain, snapshotName) {
  def common = new com.mirantis.mk.Common()
  def salt = new com.mirantis.mk.Salt()

  def target =  salt.getNodeProvider(env, domain)

  // TODO: gracefully migrate all workloads from VM, and stop it
  salt.runSaltProcessStep(env, target, 'virt.shutdown', [domain], null, true, 3600)

  //TODO: wait while VM is powered off

  common.infoMsg("Creating snapshot ${snapshotName} for VM ${domain} on node ${target}")
  salt.runSaltProcessStep(env, target, 'virt.snapshot', [domain, snapshotName], null, true, 3600)
}

def revertSnapshotVM(env, domain, snapshotName, ensureUp=true) {
  def common = new com.mirantis.mk.Common()
  def salt = new com.mirantis.mk.Salt()

  def target =  salt.getNodeProvider(env, domain)

  common.infoMsg("Reverting snapshot ${snapshotName} for VM ${domain} on node ${target}")
  salt.runSaltProcessStep(env, target, 'virt.revert_snapshot', [snapshotName, domain], null, true, 3600)

  if (ensureUp){
    salt.runSaltProcessStep(env, target, 'virt.start', [domain], null, true, 300)
  }
}

def env = "env"
timeout(time: 12, unit: 'HOURS') {
  node() {

    stage('Setup virtualenv for Pepper') {
      python.setupPepperVirtualenv(env, SALT_MASTER_URL, SALT_MASTER_CREDENTIALS)
    }

    def upgradeTargets = salt.getMinionsSorted(env, TARGET_SERVERS)

    if (upgradeTargets.isEmpty()) {
      error("No servers for upgrade matched by ${TARGET_SERVERS}")
    }

    common.printStageMap(upgradeStageMap)
    if (interactive){
      input message: common.getColorizedString(
        "Above you can find detailed info this pipeline will execute.\nThe info provides brief description of each stage, actions that will be performed and service/workload impact during each stage.\nPlease read it carefully.", "yellow")
    }


    for (target in upgradeTargets){
      common.stageWrapper(upgradeStageMap, "Pre upgrade", target, interactive) {
        openstack.runOpenStackUpgradePhase(env, target, 'pre')
      }
    }

    for (target in upgradeTargets) {
      common.stageWrapper(upgradeStageMap, "Stop OpenStack services", target, interactive) {
        stopOpenStackServices(env, target)
      }
    }

    for (target in upgradeTargets) {
      common.stageWrapper(upgradeStageMap, "Upgrade OpenStack", target, interactive) {
        openstack.runOpenStackUpgradePhase(env, target, 'upgrade')
        openstack.applyOpenstackAppsStates(env, target)
        openstack.runOpenStackUpgradePhase(env, target, 'verify')
      }

      common.stageWrapper(upgradeStageMap, "Upgrade OS", target, interactive) {
        if (OS_DIST_UPGRADE.toBoolean() == true){
          upgrade_mode = 'dist-upgrade'
        } else if (OS_UPGRADE.toBoolean() == true){
          upgrade_mode = 'upgrade'
        }
        if (OS_DIST_UPGRADE.toBoolean() == true || OS_UPGRADE.toBoolean() == true) {
          debian.osUpgradeNode(env, target, upgrade_mode, false)
        }
        openstack.applyOpenstackAppsStates(env, target)
        openstack.runOpenStackUpgradePhase(env, target, 'verify')
      }
    }
  }
}
