From null at cozmixng.org Sun Jan 1 08:23:22 2012 From: null at cozmixng.org (null at cozmixng.org) Date: Sun, 01 Jan 2012 22:23:22 +0900 Subject: [activeldap-commit] activeldap/activeldap [master] [benchmark] use meaningful file name. Message-ID: <20120101132420.3D77C9A09D@jenkins.clear-code.com> Kouhei Sutou 2012-01-01 22:23:22 +0900 (Sun, 01 Jan 2012) New Revision: 18798e34bbd7eb94cb031098287a8fda33d2c9df Log: [benchmark] use meaningful file name. Renamed files: benchmark/bench-backend.rb (from benchmark/bench-al.rb) Renamed: benchmark/bench-backend.rb (+0 -0) 100% =================================================================== From null at cozmixng.org Sun Jan 1 23:24:13 2012 From: null at cozmixng.org (null at cozmixng.org) Date: Mon, 02 Jan 2012 13:24:13 +0900 Subject: [activeldap-commit] activeldap/activeldap [master] [benchmark] use ActiveLdap::Populate. Message-ID: <20120102042510.717529A0A0@jenkins.clear-code.com> Kouhei Sutou 2012-01-02 13:24:13 +0900 (Mon, 02 Jan 2012) New Revision: ae50690a81d089c76a636d2b9ff157cd0f248ca3 Log: [benchmark] use ActiveLdap::Populate. Modified files: benchmark/bench-backend.rb Modified: benchmark/bench-backend.rb (+1 -21) =================================================================== --- benchmark/bench-backend.rb 2012-01-02 13:24:01 +0900 (a5b8d8d) +++ benchmark/bench-backend.rb 2012-01-02 13:24:13 +0900 (e0d5815) @@ -133,27 +133,7 @@ rescue LoadError end def populate_base - suffixes = [] - ActiveLdap::Base.base.split(/,/).reverse_each do |suffix| - prefix = suffixes.join(",") - suffixes.unshift(suffix) - name, value = suffix.split(/=/, 2) - next unless name == "dc" - dc_class = Class.new(ActiveLdap::Base) - dc_class.ldap_mapping :dn_attribute => "dc", - :prefix => "", - :scope => :base, - :classes => ["top", "dcObject", "organization"] - dc_class.instance_variable_set("@base", prefix) - next if dc_class.exists?(value, :prefix => "dc=#{value}") - dc = dc_class.new(value) - dc.o = dc.dc - begin - dc.save - rescue ActiveLdap::OperationNotPermitted - end - end - + ActiveLdap::Populate.ensure_base if ActiveLdap::Base.search.empty? raise "Can't populate #{ActiveLdap::Base.base}" end From null at cozmixng.org Sun Jan 1 23:24:26 2012 From: null at cozmixng.org (null at cozmixng.org) Date: Mon, 02 Jan 2012 13:24:26 +0900 Subject: [activeldap-commit] activeldap/activeldap [master] [benchmark] ignore per-user configuration file. Message-ID: <20120102042510.7D8BD9A0A5@jenkins.clear-code.com> Kouhei Sutou 2012-01-02 13:24:26 +0900 (Mon, 02 Jan 2012) New Revision: 6920bd05169062e4c6c2341194faf90fa8f60eaf Log: [benchmark] ignore per-user configuration file. Modified files: .gitignore Modified: .gitignore (+1 -0) =================================================================== --- .gitignore 2012-01-02 13:24:13 +0900 (d790881) +++ .gitignore 2012-01-02 13:24:26 +0900 (fda913f) @@ -7,3 +7,4 @@ /doc/po/*.pot /pkg/ /*.gemspec +/benchmark/config.yaml From null at cozmixng.org Sun Jan 1 23:46:23 2012 From: null at cozmixng.org (null at cozmixng.org) Date: Mon, 02 Jan 2012 13:46:23 +0900 Subject: [activeldap-commit] activeldap/activeldap [master] [benchmark] add for GitHub#26 Message-ID: <20120102044641.0D9089A09D@jenkins.clear-code.com> Kouhei Sutou 2012-01-02 13:46:23 +0900 (Mon, 02 Jan 2012) New Revision: 19752f7ed33fd08cce8342eebf47fbe0cd5d4965 Log: [benchmark] add for GitHub#26 Added files: benchmark/bench-instantiate.rb Added: benchmark/bench-instantiate.rb (+98 -0) 100644 =================================================================== --- /dev/null +++ benchmark/bench-instantiate.rb 2012-01-02 13:46:23 +0900 (06412dc) @@ -0,0 +1,98 @@ +base = File.dirname(__FILE__) +$LOAD_PATH.unshift(File.expand_path(base)) +$LOAD_PATH.unshift(File.expand_path(File.join(base, "..", "lib"))) + +require "active_ldap" +require "benchmark" + +include ActiveLdap::GetTextSupport + +argv = ARGV.dup +unless argv.include?("--config") + argv.unshift("--config", File.join(base, "config.yaml")) +end +argv, opts, options = ActiveLdap::Command.parse_options(argv) do |opts, options| + options.prefix = "ou=People" + + opts.on("--prefix=PREFIX", + _("Specify prefix for benchmarking"), + _("(default: %s)") % options.prefix) do |prefix| + options.prefix = prefix + end +end + +ActiveLdap::Base.setup_connection +config = ActiveLdap::Base.configuration + +LDAP_PREFIX = options.prefix +LDAP_USER = config[:bind_dn] +LDAP_PASSWORD = config[:password] + +N_USERS = 100 + +class ALUser < ActiveLdap::Base + ldap_mapping :dn_attribute => 'uid', :prefix => LDAP_PREFIX, + :classes => ['posixAccount', 'person'] +end + +def populate_base + ActiveLdap::Populate.ensure_base + if ActiveLdap::Base.search.empty? + raise "Can't populate #{ActiveLdap::Base.base}" + end +end + +def populate_users + ou_class = Class.new(ActiveLdap::Base) + ou_class.ldap_mapping :dn_attribute => "ou", + :prefix => "", + :classes => ["top", "organizationalUnit"] + ou_class.new(LDAP_PREFIX.split(/=/)[1]).save! + + N_USERS.times do |i| + name = i.to_s + user = ALUser.new(name) + user.uid_number = 100000 + i + user.gid_number = 100000 + i + user.cn = name + user.sn = name + user.home_directory = "/nonexistent" + user.save! + end +end + +def populate + populate_base + populate_users +end + +def main(do_populate) + if do_populate + puts(_("Populating...")) + dumped_data = ActiveLdap::Base.dump(:scope => :sub) + ActiveLdap::Base.delete_all(nil, :scope => :sub) + populate + puts + end + + Benchmark.bmbm(20) do |x| + n = 10 + GC.start + x.report("search 100 entries") do + n.times {ALUser.search} + end + GC.start + x.report("instantiate 1 entry") do + n.times {ALUser.first} + end + end +ensure + if do_populate + puts + puts(_("Cleaning...")) + ActiveLdap::Base.delete_all(nil, :scope => :sub) + ActiveLdap::Base.load(dumped_data) + end +end + +main(LDAP_USER && LDAP_PASSWORD) From null at cozmixng.org Sun Jan 1 23:24:01 2012 From: null at cozmixng.org (null at cozmixng.org) Date: Mon, 02 Jan 2012 13:24:01 +0900 Subject: [activeldap-commit] activeldap/activeldap [master] [benchmark] load bencharmk/config.yaml automatically. Message-ID: <20120102042510.581F69A09D@jenkins.clear-code.com> Kouhei Sutou 2012-01-02 13:24:01 +0900 (Mon, 02 Jan 2012) New Revision: d2639f0ae23fb72b6babafda18f3148186188d9a Log: [benchmark] load bencharmk/config.yaml automatically. Modified files: benchmark/bench-backend.rb Modified: benchmark/bench-backend.rb (+5 -1) =================================================================== --- benchmark/bench-backend.rb 2012-01-01 22:23:22 +0900 (36361ba) +++ benchmark/bench-backend.rb 2012-01-02 13:24:01 +0900 (a5b8d8d) @@ -7,7 +7,11 @@ require "benchmark" include ActiveLdap::GetTextSupport -argv, opts, options = ActiveLdap::Command.parse_options do |opts, options| +argv = ARGV.dup +unless argv.include?("--config") + argv.unshift("--config", File.join(base, "config.yaml")) +end +argv, opts, options = ActiveLdap::Command.parse_options(argv) do |opts, options| options.prefix = "ou=People" opts.on("--prefix=PREFIX", From null at cozmixng.org Sun Jan 1 23:24:47 2012 From: null at cozmixng.org (null at cozmixng.org) Date: Mon, 02 Jan 2012 13:24:47 +0900 Subject: [activeldap-commit] activeldap/activeldap [master] [benchmark] add README for running benchmarks. Message-ID: <20120102042510.886E89A0A6@jenkins.clear-code.com> Kouhei Sutou 2012-01-02 13:24:47 +0900 (Mon, 02 Jan 2012) New Revision: ecf2520b785a62a1f4f1d23cb72dea3888f37b0a Log: [benchmark] add README for running benchmarks. Added files: benchmark/README.md Modified files: benchmark/config.yaml.sample Added: benchmark/README.md (+64 -0) 100644 =================================================================== --- /dev/null +++ benchmark/README.md 2012-01-02 13:24:47 +0900 (598afbb) @@ -0,0 +1,64 @@ +# README + +This document describes how to run benchmarks under +benchmark/ directory. + +## Configure your LDAP server + +You need a LDAP server to run benchmarks. This is dependes +on your environment. + +In this document, we assume that you configure your LDAP +server by the following configuration: + +* host: 127.0.0.1 +* base DN: dc=bench,dc=local +* encryption: startTLS +* bind DN: cn=admin,dc=local +* password: secret + +## Configure ActiveLdap to connect to your LDAP server + +You need an ActiveLdap configuration in +benchmark/config.yaml to connect to your LDAP server. There +is a sample configuration in +benchmark/config.yaml.sample. It's good to start from it. + + % cp benchmark/config.yaml.sample benchmark/config.yaml + % editor benchmark/config.yaml + +The configuration uses the same format of ldap.yaml. + +## Run benchmarks + +You just run a bencmark script. It loads +benchmark/config.yaml and populate benchmark data automatically. + + % ruby benchmark/bench-backend.rb + Populating... + + Rehearsal --------------------------------------------------------------- + 1x: AL(LDAP) 0.220000 0.000000 0.220000 ( 0.234775) + 1x: AL(Net::LDAP) 0.280000 0.000000 0.280000 ( 0.273048) + 1x: AL(LDAP: No Obj) 0.000000 0.000000 0.000000 ( 0.009217) + 1x: AL(Net::LDAP: No Obj) 0.060000 0.000000 0.060000 ( 0.056727) + 1x: LDAP 0.000000 0.000000 0.000000 ( 0.003261) + 1x: Net::LDAP 0.040000 0.000000 0.040000 ( 0.029862) + ------------------------------------------------------ total: 0.600000sec + + user system total real + 1x: AL(LDAP) 0.200000 0.000000 0.200000 ( 0.195660) + 1x: AL(Net::LDAP) 0.220000 0.000000 0.220000 ( 0.213444) + 1x: AL(LDAP: No Obj) 0.010000 0.000000 0.010000 ( 0.009000) + 1x: AL(Net::LDAP: No Obj) 0.030000 0.000000 0.030000 ( 0.026847) + 1x: LDAP 0.000000 0.000000 0.000000 ( 0.003377) + 1x: Net::LDAP 0.020000 0.000000 0.020000 ( 0.022662) + + Entries processed by Ruby/ActiveLdap + LDAP: 100 + Entries processed by Ruby/ActiveLdap + Net::LDAP: 100 + Entries processed by Ruby/ActiveLdap + LDAP: (without object creation): 100 + Entries processed by Ruby/ActiveLdap + Net::LDAP: (without object creation): 100 + Entries processed by Ruby/LDAP: 100 + Entries processed by Net::LDAP: 100 + + Cleaning... Modified: benchmark/config.yaml.sample (+2 -2) =================================================================== --- benchmark/config.yaml.sample 2012-01-02 13:24:26 +0900 (fbf7a9e) +++ benchmark/config.yaml.sample 2012-01-02 13:24:47 +0900 (96cd467) @@ -1,5 +1,5 @@ host: 127.0.0.1 -base: dc=bench,dc=localcomain +base: dc=bench,dc=local method: :tls -bind_dn: cn=user-name,dc=localdomain +bind_dn: cn=admin,dc=local password: secret From null at cozmixng.org Mon Jan 2 21:30:29 2012 From: null at cozmixng.org (null at cozmixng.org) Date: Tue, 03 Jan 2012 11:30:29 +0900 Subject: [activeldap-commit] activeldap/activeldap [master] Merge pull request #26 from mihu/dirty_speed_fix Message-ID: <20120103023034.0DA689A0A0@jenkins.clear-code.com> Kouhei Sutou 2012-01-03 11:30:29 +0900 (Tue, 03 Jan 2012) New Revision: 84c36c7209e043d66e582015e3167bbd1a7b14ee Log: Merge pull request #26 from mihu/dirty_speed_fix dirty - speed fix Patch by mihu. Thanks!!! From null at cozmixng.org Tue Jan 3 07:18:40 2012 From: null at cozmixng.org (null at cozmixng.org) Date: Tue, 03 Jan 2012 21:18:40 +0900 Subject: [activeldap-commit] activeldap/activeldap [master] cut down model initialization time Message-ID: <20120104031424.C65819A09D@jenkins.clear-code.com> mihu 2012-01-03 21:18:40 +0900 (Tue, 03 Jan 2012) New Revision: cf8617384632db64e7b786126c55b9e083609b5f Merged 5d8bf2a: Merge pull request #28 from mihu/cut_down_init Log: cut down model initialization time The main idea is to go around hooked setter and slow respond_to? method. The bonus is remove of find_every from dirty module, because we are no longer dirtying when initializing. Modified files: benchmark/bench-instantiate.rb lib/active_ldap/attribute_methods/dirty.rb lib/active_ldap/base.rb Modified: benchmark/bench-instantiate.rb (+1 -1) =================================================================== --- benchmark/bench-instantiate.rb 2012-01-03 11:30:29 +0900 (06412dc) +++ benchmark/bench-instantiate.rb 2012-01-03 21:18:40 +0900 (3550b46) @@ -76,7 +76,7 @@ def main(do_populate) end Benchmark.bmbm(20) do |x| - n = 10 + n = 100 GC.start x.report("search 100 entries") do n.times {ALUser.search} Modified: lib/active_ldap/attribute_methods/dirty.rb (+0 -10) =================================================================== --- lib/active_ldap/attribute_methods/dirty.rb 2012-01-03 11:30:29 +0900 (735e273) +++ lib/active_ldap/attribute_methods/dirty.rb 2012-01-03 21:18:40 +0900 (49e464b) @@ -28,16 +28,6 @@ module ActiveLdap end end - module ClassMethods - # dirty/clear hack - # only place that we can ovveride and hook to clear the new record - def find_every(options) - super.each do |result| - result.changed_attributes.clear - end - end - end - protected def attribute=(attr, val) attribute_will_change!(attr) unless val == get_attribute(attr) Modified: lib/active_ldap/base.rb (+16 -1) =================================================================== --- lib/active_ldap/base.rb 2012-01-03 11:30:29 +0900 (4c22d90) +++ lib/active_ldap/base.rb 2012-01-03 21:18:40 +0900 (89661cf) @@ -1065,10 +1065,25 @@ module ActiveLdap classes, attributes = extract_object_class(attributes) self.classes = classes self.dn = dn - self.attributes = attributes + initialize_attributes(attributes) yield self if block_given? end + def initialize_attributes(attributes) + _schema = _local_entry_attribute = nil + targets = sanitize_for_mass_assignment(attributes) + targets.each do |key, value| + unless have_attribute?(key) + _schema ||= schema + attribute = _schema.attribute(key) + _local_entry_attribute ||= local_entry_attribute + _local_entry_attribute.register(attribute) + end + set_attribute(key, value) + end + end + private :initialize_attributes + def instantiate(args) dn, attributes, options = args options ||= {} From null at cozmixng.org Tue Jan 3 22:14:10 2012 From: null at cozmixng.org (null at cozmixng.org) Date: Wed, 04 Jan 2012 12:14:10 +0900 Subject: [activeldap-commit] activeldap/activeldap [master] Merge pull request #28 from mihu/cut_down_init Message-ID: <20120104031424.E7E749A0A0@jenkins.clear-code.com> Kouhei Sutou 2012-01-04 12:14:10 +0900 (Wed, 04 Jan 2012) New Revision: 5d8bf2a9417ab555bec69b5647723f12e4d32833 Log: Merge pull request #28 from mihu/cut_down_init cut down model initialization time Patch by mihu. Thanks!!! From null at cozmixng.org Sat Jan 14 03:45:43 2012 From: null at cozmixng.org (null at cozmixng.org) Date: Sat, 14 Jan 2012 17:45:43 +0900 Subject: [activeldap-commit] activeldap/activeldap [master] add activeldap.gemspec. Message-ID: <20120114084555.BAAD99A09D@jenkins.clear-code.com> Kouhei Sutou 2012-01-14 17:45:43 +0900 (Sat, 14 Jan 2012) New Revision: 9b3d49ec67634706cdde091324ec976d73cf9b38 Log: add activeldap.gemspec. GitHub: fixes #29 Suggested by mklappstuhl. Thanks!!! Added files: activeldap.gemspec lib/active_ldap/version.rb Modified files: .gitignore Gemfile Rakefile lib/active_ldap.rb Modified: .gitignore (+0 -1) =================================================================== --- .gitignore 2012-01-04 12:14:10 +0900 (fda913f) +++ .gitignore 2012-01-14 17:45:43 +0900 (0c21fa4) @@ -6,5 +6,4 @@ /doc/html/activeldap/ /doc/po/*.pot /pkg/ -/*.gemspec /benchmark/config.yaml Modified: Gemfile (+1 -14) =================================================================== --- Gemfile 2012-01-04 12:14:10 +0900 (7215d80) +++ Gemfile 2012-01-14 17:45:43 +0900 (2250adf) @@ -2,17 +2,4 @@ source "http://rubygems.org" -gem 'activemodel', '~> 3.1.0' -gem 'locale' -gem 'fast_gettext' -gem 'gettext_i18n_rails' - -group :development, :test do - gem 'ruby-ldap' - gem 'net-ldap' - gem 'jeweler' - gem 'test-unit' - gem 'test-unit-notify' - gem "yard" - gem "RedCloth" -end +gemspec Modified: Rakefile (+5 -35) =================================================================== --- Rakefile 2012-01-04 12:14:10 +0900 (9e79b17) +++ Rakefile 2012-01-14 17:45:43 +0900 (f2709ff) @@ -2,6 +2,8 @@ require 'thread' require 'find' +require 'pathname' +require 'erb' require 'rubygems' require 'bundler/setup' @@ -13,43 +15,11 @@ require "yard" $KCODE = "u" if RUBY_VERSION < "1.9" -base_dir = File.expand_path(File.dirname(__FILE__)) -$LOAD_PATH.unshift(File.join(base_dir, 'lib')) -require 'active_ldap' - project_name = "ActiveLdap" -ENV["VERSION"] ||= ActiveLdap::VERSION -version = ENV["VERSION"] -spec = nil -Jeweler::Tasks.new do |_spec| - spec = _spec - spec.name = 'activeldap' - spec.version = version.dup - spec.rubyforge_project = 'ruby-activeldap' - spec.authors = ['Will Drewry', 'Kouhei Sutou'] - spec.email = ['redpig at dataspill.org', 'kou at cozmixng.org'] - spec.summary = 'ActiveLdap is a object-oriented API to LDAP' - spec.homepage = 'http://ruby-activeldap.rubyforge.org/' - spec.files = FileList["lib/**/*", - "{benchmark,examples,po}/**/*", - "bin/*", - "doc/text/**/*", - "COPYING", - "Gemfile", - "LICENSE", - "README.textile", - "TODO"] - spec.test_files = FileList['test/test_*.rb'] - spec.description = <<-EOF - 'ActiveLdap' is a ruby library which provides a clean - objected oriented interface to the Ruby/LDAP library. It was inspired - by ActiveRecord. This is not nearly as clean or as flexible as - ActiveRecord, but it is still trivial to define new objects and manipulate - them with minimal difficulty. - EOF - spec.license = "Ruby's or GPLv2 or later" -end +gemspec_file = Pathname.new(__FILE__).dirname + "activeldap.gemspec" +spec = eval(gemspec_file.read) +Jeweler::Tasks.new(spec) Rake::Task["release"].prerequisites.clear Jeweler::RubygemsDotOrgTasks.new do Added: activeldap.gemspec (+54 -0) 100644 =================================================================== --- /dev/null +++ activeldap.gemspec 2012-01-14 17:45:43 +0900 (b99b4de) @@ -0,0 +1,54 @@ +# -*- mode: ruby; coding: utf-8 -*- + +Gem::Specification.new do |spec| + base_dir = File.expand_path(File.dirname(__FILE__)) + $LOAD_PATH.unshift(File.join(base_dir, 'lib')) + require 'active_ldap/version' + + collect_files = lambda do |*globs| + files = [] + globs.each do |glob| + files.concat(Dir.glob(glob)) + end + files.uniq.sort + end + + spec.name = 'activeldap' + spec.version = ActiveLdap::VERSION.dup + spec.rubyforge_project = 'ruby-activeldap' + spec.authors = ['Will Drewry', 'Kouhei Sutou'] + spec.email = ['redpig at dataspill.org', 'kou at cozmixng.org'] + spec.summary = 'ActiveLdap is a object-oriented API to LDAP' + spec.homepage = 'http://ruby-activeldap.rubyforge.org/' + spec.files = collect_files.call("lib/**/*", + "{benchmark,examples,po}/**/*", + "bin/*", + "doc/text/**/*", + "COPYING", + "Gemfile", + "LICENSE", + "README.textile", + "TODO") + spec.files.delete_if {|file| /\.yaml\z/ =~ File.basename(file)} + spec.description = <<-EOF + 'ActiveLdap' is a ruby library which provides a clean + objected oriented interface to the Ruby/LDAP library. It was inspired + by ActiveRecord. This is not nearly as clean or as flexible as + ActiveRecord, but it is still trivial to define new objects and manipulate + them with minimal difficulty. + EOF + spec.license = "Ruby's or GPLv2 or later" + + spec.add_dependency("activemodel", ["~> 3.1.0"]) + spec.add_dependency("locale") + spec.add_dependency("fast_gettext") + spec.add_dependency("gettext_i18n_rails") + + spec.add_development_dependency("ruby-ldap") + spec.add_development_dependency("net-ldap") + spec.add_development_dependency("jeweler") + spec.add_development_dependency("test-unit") + spec.add_development_dependency("test-unit-notify") + spec.add_development_dependency("yard") + spec.add_development_dependency("RedCloth") +end Modified: lib/active_ldap.rb (+2 -1) =================================================================== --- lib/active_ldap.rb 2012-01-04 12:14:10 +0900 (01b9b25) +++ lib/active_ldap.rb 2012-01-14 17:45:43 +0900 (8d3ca8c) @@ -2,8 +2,9 @@ require "rubygems" require "active_model" require "active_support/core_ext" +require "active_ldap/version" + module ActiveLdap - VERSION = "3.1.2" autoload :Command, "active_ldap/command" end Added: lib/active_ldap/version.rb (+3 -0) 100644 =================================================================== --- /dev/null +++ lib/active_ldap/version.rb 2012-01-14 17:45:43 +0900 (54c90e9) @@ -0,0 +1,3 @@ +module ActiveLdap + VERSION = "3.1.2" +end From null at cozmixng.org Sat Jan 14 09:23:41 2012 From: null at cozmixng.org (null at cozmixng.org) Date: Sat, 14 Jan 2012 23:23:41 +0900 Subject: [activeldap-commit] activeldap/activeldap [master] [doc] fix ActiveLdap::Base.setup_connection documentation. Message-ID: <20120114142357.9D1729A09D@jenkins.clear-code.com> Kouhei Sutou 2012-01-14 23:23:41 +0900 (Sat, 14 Jan 2012) New Revision: 491fcf7531efa24c086e3e1c0da2014f87adb8ec Log: [doc] fix ActiveLdap::Base.setup_connection documentation. Modified files: lib/active_ldap/base.rb Modified: lib/active_ldap/base.rb (+3 -2) =================================================================== --- lib/active_ldap/base.rb 2012-01-14 17:45:43 +0900 (89661cf) +++ lib/active_ldap/base.rb 2012-01-14 23:23:41 +0900 (22615e7) @@ -344,8 +344,9 @@ module ActiveLdap end end - # Connect and bind to LDAP creating a class variable for use by - # all ActiveLdap objects. + # Set LDAP connection configuration up. It doesn't connect + # and bind to LDAP server. A connection to LDAP server is + # created when it's needed. # # == +config+ # +config+ must be a hash that may contain any of the following fields: