# -*- coding: utf-8 -*-

require Dcmgr::DCMGR_ROOT + '/lib/dcmgr/drivers/openvz_config.rb'

module Dcmgr
  module Drivers
    class Openvz < LinuxContainer
      include Dcmgr::Logger
      include Dcmgr::Helpers::NicHelper

      # API policy information for openvz hypervisor.
      class Policy < HypervisorPolicy

        def validate_instance_model(instance)
        end

        def validate_volume_model(volume)
        end

        def on_associate_volume(instance, volume)
          # work around: set pkey to fill unique value.
          volume.guest_device_name = volume.pk
        end

      end

      def self.policy
        Policy.new
      end

      def self.local_store_class
        LinuxLocalStore
      end

      template_base_dir "openvz"

      def_configuration do
        param :ctid_offset, :default=>100

        def validate(errors)
          if @config[:ctid_offset].to_i < 100
            errors << "ctid_offset must be larger than or equal to 100: #{@config[:ctid_offset]}"
          end
        end
      end

      # Decorator pattern class of Rpc::HvaHandler::HvaContext.
      class OvzContext
        def initialize(root_ctx)
          raise ArgumentError unless root_ctx.is_a?(Rpc::HvaContext)
          @subject = root_ctx
          @ovz_config = OpenvzConfig.new
        end

        attr_reader :ovz_config
        alias :config :ovz_config

        def ctid
          Drivers::Openvz.driver_configuration.ctid_offset + inst[:id].to_i
        end

        def private_dir
          File.expand_path(ctid.to_s, config.ve_private)
        end

        def ct_umount_path
          File.expand_path("#{ctid}.umount", config.ve_config_dir)
        end

        def ct_mount_path
          File.expand_path("#{ctid}.mount", config.ve_config_dir)
        end

        def ct_conf_path
          File.expand_path("#{ctid}.conf", config.ve_config_dir)
        end

        def ct_local_confs
          [ct_conf_path, ct_mount_path, ct_umount_path]
        end

        def cgroup_scope
          ctid.to_s
        end

        private
        def method_missing(meth, *args)
          @subject.send(meth, *args)
        end
      end

      before do
        @args = @args.map {|i|  i.is_a?(Rpc::HvaContext) ? OvzContext.new(i) : i; }
        # First arugment is expected a HvaContext.
        @hc = @args.first
      end

      def run_instance(hc)
        # write a openvz container id
        inst = hc.inst
        ctid_file_path = File.expand_path('openvz.ctid', hc.inst_data_dir)

        File.open(ctid_file_path, "w") { |f|
          f.write(hc.ctid)
        }
        logger.debug("write a openvz container id #{ctid_file_path}")

        # delete old config file
        if File.exists?(hc.ct_conf_path)
          File.unlink(hc.ct_conf_path)
          logger.debug("old config file was deleted #{hc.ct_conf_path}")
        end
        if File.exists?(hc.ct_mount_path)
          File.unlink(hc.ct_mount_path)
          logger.debug("old mount file was deleted #{hc.ct_mount_path}")
        end

        destroy_config_file_path = "#{hc.ct_conf_path}.destroyed"
        destroy_mount_file_path = "#{hc.ct_mount_path}.destroyed"
        if File.exists?(destroy_config_file_path)
          File.unlink(destroy_config_file_path)
          logger.debug("old config file was deleted #{destroy_config_file_path}")
        end
        if File.exists?(destroy_mount_file_path)
          File.unlink(destroy_mount_file_path)
          logger.debug("old mount file was deleted #{destroy_config_file_path}")
        end

        # generate openvz config files
        generate_config(hc)

        # create openvz container
        image = inst[:image]
        # create mount directory
        FileUtils.mkdir(hc.private_dir) unless File.exists?(hc.private_dir)
        mount_root_image(hc, hc.private_dir)

        # mount metadata drive
        metadata_path = "#{hc.inst_data_dir}/metadata"
        Dir.mkdir(metadata_path) unless File.exists?(metadata_path)
        mount_metadata_drive(hc, metadata_path)

        # set name
        sh("vzctl set %s --name %s --save",[hc.ctid, hc.inst_id])
        #
        # Name="i-xxxx"
        #

        # setup openvz config file
        vifs = inst[:vif]

        # set virtual interface
        if !vifs.empty?
          vifs.sort {|a, b| a[:device_index] <=> b[:device_index]}.each {|vif|
            ifname = "eth#{vif[:device_index]}"
            mac = vif[:mac_addr].unpack('A2'*6).join(':')
            # host_mac become a randomly generated MAC Address.
            host_mac = nil
            if vif[:ipv4] && vif[:ipv4][:network]
              bridge = bridge_if_name(vif[:ipv4][:network][:dc_network])
              sh("vzctl set %s --netif_add %s,%s,%s,%s,%s --save",[hc.inst_id, ifname, mac, vif_uuid(vif), host_mac, bridge])
            end
            #
            # NETIF="ifname=eth0,bridge=vzbr0,mac=52:54:00:68:BB:AC,host_ifname=vif-h63jg7pp,host_mac=52:54:00:68:BB:AC"
            #
          }
        end
        # set cpus
        sh("vzctl set %s --cpus %s --save",[hc.inst_id, inst[:cpu_cores]])
        #
        # CPUS="1"
        #

        # set memory size
        sh("vzctl set %s --privvmpage %s --save",[hc.inst_id, (inst[:memory_size] * 256)])
        #
        # PRIVVMPAGES="65536"
        #
        sh("vzctl set %s --vmguarpages %s --save",[hc.inst_id, (inst[:memory_size] * 256)])
        #
        # VMGUARPAGES="65536"
        #

        # start openvz container
        sh("vzctl start %s",[hc.inst_id])
        hc.logger.info("Started container")

        # Set blkio throttling policy to vm_data_dir block device.
        cgroup_set('blkio', hc.cgroup_scope) do |c|
          devid = c.find_devnode_id(hc.inst_data_dir)

          c.add('blkio.throttle.read_iops_device', "#{devid} #{driver_configuration.cgroup_blkio.read_iops.to_i}")
          c.add('blkio.throttle.read_bps_device', "#{devid} #{driver_configuration.cgroup_blkio.read_bps.to_i}")
          c.add('blkio.throttle.write_iops_device', "#{devid} #{driver_configuration.cgroup_blkio.write_iops.to_i}")
          c.add('blkio.throttle.write_bps_device', "#{devid} #{driver_configuration.cgroup_blkio.write_bps.to_i}")
        end
      end

      def terminate_instance(hc)
        poweroff_instance(hc)

        # delete container folder
        sh("vzctl destroy %s",[hc.inst_id])
        cleanup_vif(hc)
        hc.logger.debug("delete container folder #{hc.private_dir}")
        # delete CT local config files
        hc.ct_local_confs.map { |i| i + ".destroyed" }.each { |i|
          if File.exist?(i)
            File.unlink(i) rescue nil
            hc.logger.info("Deleted CT config: #{File.basename(i)}")
          else
            hc.logger.warn("#{File.basename(i)} does not exist")
          end
        }

        hc.logger.info("Terminated successfully.")
      end

      def reboot_instance(hc)
        SkipCheckHelper.skip_check(hc.inst_id) {
          sh("vzctl restart %s", [hc.inst_id])
          hc.logger.info("Restarted container.")
        }
      end

      def poweroff_instance(hc)
        # stop container
        sh("vzctl stop %s",[hc.inst_id])

        # wait stopped of container status
        tryagain do
          sh("vzctl status %s", [hc.inst_id])[:stdout].chomp.include?("down")
        end
        hc.logger.info("Stop container.")

        umount_root_image(hc, hc.private_dir)
        umount_metadata_drive(hc, File.expand_path('metadata', hc.inst_data_dir))
      end

      def poweron_instance(hc)
        run_instance(hc)
      end

      def check_instance(i)
        if SkipCheckHelper.skip_check?(i)
          logger.info("Skip check_instance during reboot process: #{i}")
          return
        end

        container_status = `vzctl status #{i}`.chomp.split(" ")[4]
        if container_status != "running"
          raise "Unable to find the openvz container: #{i}"
        end
      end

      private
      def generate_config(hc)
        # generate openvz config
        output_file_path = "#{hc.config.ve_config_dir}/ve-openvz.conf-sample"
        render_template('template.conf', output_file_path, binding)
        raise "config file does not exist #{output_file_path}" unless File.exists?(output_file_path)
        FileUtils.cp(output_file_path, hc.ct_conf_path)
        logger.debug("created config #{output_file_path}")

        # template variables
        ve_metadata_path = "#{hc.inst_data_dir}/metadata"
        hn_metadata_path = "#{hc.config.ve_root}/#{hc.ctid}/metadata"

        if Dcmgr::Configurations.hva.edge_networking == 'openvnet'
          render_template('template-vnet.mount', hc.ct_mount_path, binding)
          render_template('template-vnet.umount', hc.ct_umount_path, binding)
        else
          render_template('template.mount', hc.ct_mount_path, binding)
          render_template('template.umount', hc.ct_umount_path, binding)
        end
        sh("chmod +x %s", [hc.ct_umount_path])
        sh("chmod +x %s", [hc.ct_mount_path])
        hc.logger.info("Created CT config: #{hc.ct_mount_path}")
      end

      Task::Tasklet.register(self) {
        self.new
      }
    end
  end
end
