text: # -*- coding: utf-8 -*- {{{
  # vim: set fenc=utf-8 ft=python sw=4 ts=4 sts=4 et:
  #
  # Copyright 2020, Battelle Memorial Institute.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  # This material was prepared as an account of work sponsored by an agency of
  # the United States Government. Neither the United States Government nor the
  # United States Department of Energy, nor Battelle, nor any of their
  # employees, nor any jurisdiction or organization that has cooperated in the
  # development of these materials, makes any warranty, express or
  # implied, or assumes any legal liability or responsibility for the accuracy,
  # completeness, or usefulness or any information, apparatus, product,
  # software, or process disclosed, or represents that its use would not infringe
  # privately owned rights. Reference herein to any specific commercial product,
  # process, or service by trade name, trademark, manufacturer, or otherwise
  # does not necessarily constitute or imply its endorsement, recommendation, or
  # favoring by the United States Government or any agency thereof, or
  # Battelle Memorial Institute. The views and opinions of authors expressed
  # herein do not necessarily state or reflect those of the
  # United States Government or any agency thereof.
  #
  # PACIFIC NORTHWEST NATIONAL LABORATORY operated by
  # BATTELLE for the UNITED STATES DEPARTMENT OF ENERGY
  # under Contract DE-AC05-76RL01830
  # }}}
  
  
  import os
  import os.path
  import abc
  import argparse
  from shutil import copy, rmtree
  from test_settings import (virtual_device_host, device_types, config_dir, 
                             volttron_install, platform_driver_file,
                             host_config_location)
  from volttron.platform import jsonapi
  
  class DeviceConfig(object, metaclass=abc.ABCMeta):
      def __init__(self, host_address, instance_number, registry_config, interval=60, heart_beat_point=None):
          self.configuration = {"registry_config": registry_config,
                                "driver_type": self.device_type(),
                                "timezone": 'US/Pacific',
                                "interval": interval}
  
          if heart_beat_point is not None:
              self.configuration["heart_beat_point"] = heart_beat_point
          
          self.configuration["driver_config"] = self.get_driver_config(host_address, instance_number)
          
  
      def __str__(self):
          return jsonapi.dumps(self.configuration,
                            indent=4, separators=(',', ': '))
      
      @abc.abstractmethod
      def device_type(self):
          pass
  
      @staticmethod
      @abc.abstractmethod
      def get_driver_config(host_address, instance_number):
          pass
      
      @abc.abstractmethod
      def get_virtual_driver_commandline(self):
          pass
  
  class BACnetConfig(DeviceConfig):
      starting_port = 47808
          
      def device_type(self):
          return "bacnet"
          
      @staticmethod
      def get_driver_config(host_address, instance_number):
          return {"device_address": host_address + ":" + str(BACnetConfig.starting_port + instance_number)}
      
  
      def get_virtual_driver_commandline(self):
          config_file = os.path.basename(self.configuration["registry_config"])
          interface = self.configuration["driver_config"]["device_address"]
          return "bacnet.py {config} {interface}".format(config=config_file, interface=interface)
  
  class ModbusConfig(DeviceConfig):
      starting_port = 50200
          
      def device_type(self):
          return "modbus"
          
      @staticmethod
      def get_driver_config(host_address, instance_number):
          return {"device_address": host_address,
                  "port": ModbusConfig.starting_port + instance_number}
      
      def get_virtual_driver_commandline(self):
          config_file = os.path.basename(self.configuration["registry_config"])
          port = self.configuration["driver_config"]["port"]
          host_address = self.configuration["driver_config"]["device_address"]
          return "modbus.py {config} {interface} --port={port}".format(config=config_file, 
                                                                       interface=host_address, 
                                                                       port=port)
          
  class FakeConfig(DeviceConfig):        
      def device_type(self):
          return "fakedriver"
          
      @staticmethod
      def get_driver_config(host_address, instance_number):
          return {}    
  
      def get_virtual_driver_commandline(self):
          return ""
  
  device_config_classes = {"bacnet":BACnetConfig,
                           "modbus":ModbusConfig,
                           "fake":FakeConfig}
  
  def build_device_configs(device_type, host_address, count, reg_config_ref, config_dir, interval, output_path):
      #command line to start virtual devices.
      command_lines = []
  
      try:
          os.makedirs(output_path)
      except os.error:
          pass
      
      klass = device_config_classes[device_type]
      for i in range(count):
          config_instance = klass(host_address, i, reg_config_ref,
                                  interval=interval)
          
          file_name = device_type + str(i)
          file_path = os.path.join(output_path, file_name)
          
          with open(file_path, 'w') as f:
              f.write(str(config_instance)+'\n')
  
          command_lines.append(config_instance.get_virtual_driver_commandline())
          
      return command_lines
  
  def build_all_configs(device_type, host_address, count, reg_config, config_dir,
                        scalability_test, scalability_test_iterations, driver_scrape_interval,
                        publish_only_depth_all, interval, campus, building):
      '''For command line interface'''
      print(config_dir)
      
      config_dir = os.path.abspath(config_dir)
  
      registry_config_dir = os.path.join(config_dir, "registry_configs")
      devices_dir = os.path.join(config_dir, 'devices', campus, building)
  
      if os.path.exists(devices_dir):
          rmtree(devices_dir, ignore_errors=True)
  
      if os.path.exists(registry_config_dir):
          rmtree(registry_config_dir, ignore_errors=True)
  
      try:
          os.makedirs(registry_config_dir)
      except os.error:
          pass
      
  
      copy(reg_config, registry_config_dir)
  
      reg_config_ref = "config://registry_configs/" + os.path.basename(reg_config)
      
      command_lines = build_device_configs(device_type, host_address, count, reg_config_ref, config_dir, interval, devices_dir)
      
      build_platform_config(config_dir,
                          scalability_test, scalability_test_iterations,
                          driver_scrape_interval, publish_only_depth_all)
          
      
  def build_platform_config(config_dir,
                          scalability_test, scalability_test_iterations,
                          driver_scrape_interval, publish_only_depth_all):
      """Takes the input from multiple called to build_device_configs and create the master config."""
      configuration = {}
      configuration['scalability_test'] = scalability_test
      configuration['scalability_test_iterations'] = scalability_test_iterations
      configuration['driver_scrape_interval'] = driver_scrape_interval
  
      if publish_only_depth_all:
          configuration["publish_breadth_first_all"] = False
          configuration["publish_depth_first"] = False
          configuration["publish_breadth_first"] = False
          
      config_str = jsonapi.dumps(configuration, indent=4, separators=(',', ': '))
      
      agent_config = os.path.join(config_dir, "config")
      
      with open(agent_config, 'w') as f:
          f.write(config_str+'\n')
          
  if __name__ == "__main__":
      parser = argparse.ArgumentParser(description="Create driver configuration files for scalability test.")
      
      parser.add_argument('--count', type=int, default=1, 
                          help='number of devices to configure')
      
      parser.add_argument('--scalability-test', action='store_true', 
                          help='Configure platform driver for a scalability test')
      
      parser.add_argument('--publish-only-depth-all', action='store_true', 
                          help='Configure drivers to only publish depth first all.')
      
      parser.add_argument('--driver-scrape-interval', type=float, default=0.02,
                          help='Configure interval between individual device publishes.')
      
      parser.add_argument('--scalability-test-iterations', type=int, default=5, 
                          help='Scalability test iterations')
      
      parser.add_argument('device_type', choices=['bacnet', 'modbus', 'fake'], 
                          help='type of device to use for testing')
      
      parser.add_argument('registry_config', 
                          help='registry configuration to use for test devices')
      
      parser.add_argument('virtual_device_host', 
                          help='host of the test devices',
                          default=virtual_device_host)
      
      parser.add_argument('--config-dir', help='output directory for configurations',
                          default=config_dir)
      
      parser.add_argument('--interval', help='Scrape interval setting for all drivers',
                          type=float,
                          default=60.0)
      
      parser.add_argument('--campus', 
                          help='campus name used for testing',
                          default='')
      
      parser.add_argument('--building', 
                          help='building name used for testing',
                          default='')
      
      args = parser.parse_args()
      build_all_configs(args.device_type,
                        args.virtual_device_host, args.count, args.registry_config, 
                        args.config_dir, args.scalability_test, args.scalability_test_iterations,
                        args.driver_scrape_interval, args.publish_only_depth_all,
                        args.interval, args.campus, args.building)
      
      
      
  