wakame-vdc-agents 10.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/LICENSE +202 -0
  2. data/NOTICE +1 -0
  3. data/Rakefile +142 -0
  4. data/bin/hva +972 -0
  5. data/bin/nsa +147 -0
  6. data/bin/sta +182 -0
  7. data/config/hva.conf.example +10 -0
  8. data/config/initializers/isono.rb +43 -0
  9. data/config/initializers/passenger.rb +6 -0
  10. data/config/initializers/sequel.rb +21 -0
  11. data/config/nsa.conf.example +9 -0
  12. data/config/path_resolver.rb +12 -0
  13. data/lib/dcmgr.rb +115 -0
  14. data/lib/dcmgr/endpoints/core_api.rb +1004 -0
  15. data/lib/dcmgr/endpoints/core_api_mock.rb +816 -0
  16. data/lib/dcmgr/endpoints/errors.rb +55 -0
  17. data/lib/dcmgr/endpoints/metadata.rb +129 -0
  18. data/lib/dcmgr/logger.rb +44 -0
  19. data/lib/dcmgr/models/account.rb +104 -0
  20. data/lib/dcmgr/models/account_resource.rb +16 -0
  21. data/lib/dcmgr/models/base.rb +69 -0
  22. data/lib/dcmgr/models/base_new.rb +371 -0
  23. data/lib/dcmgr/models/frontend_system.rb +38 -0
  24. data/lib/dcmgr/models/host_pool.rb +102 -0
  25. data/lib/dcmgr/models/image.rb +46 -0
  26. data/lib/dcmgr/models/instance.rb +255 -0
  27. data/lib/dcmgr/models/instance_netfilter_group.rb +16 -0
  28. data/lib/dcmgr/models/instance_nic.rb +68 -0
  29. data/lib/dcmgr/models/instance_spec.rb +21 -0
  30. data/lib/dcmgr/models/ip_lease.rb +42 -0
  31. data/lib/dcmgr/models/netfilter_group.rb +88 -0
  32. data/lib/dcmgr/models/netfilter_rule.rb +21 -0
  33. data/lib/dcmgr/models/network.rb +32 -0
  34. data/lib/dcmgr/models/physical_host.rb +67 -0
  35. data/lib/dcmgr/models/request_log.rb +25 -0
  36. data/lib/dcmgr/models/ssh_key_pair.rb +55 -0
  37. data/lib/dcmgr/models/storage_pool.rb +134 -0
  38. data/lib/dcmgr/models/tag.rb +126 -0
  39. data/lib/dcmgr/models/tag_mapping.rb +28 -0
  40. data/lib/dcmgr/models/volume.rb +130 -0
  41. data/lib/dcmgr/models/volume_snapshot.rb +47 -0
  42. data/lib/dcmgr/node_modules/hva_collector.rb +134 -0
  43. data/lib/dcmgr/node_modules/sta_collector.rb +72 -0
  44. data/lib/dcmgr/scheduler.rb +12 -0
  45. data/lib/dcmgr/scheduler/find_last.rb +16 -0
  46. data/lib/dcmgr/scheduler/find_random.rb +16 -0
  47. data/lib/dcmgr/stm/instance.rb +25 -0
  48. data/lib/dcmgr/stm/snapshot_context.rb +33 -0
  49. data/lib/dcmgr/stm/volume_context.rb +65 -0
  50. data/lib/dcmgr/web/base.rb +21 -0
  51. data/lib/sinatra/accept_media_types.rb +128 -0
  52. data/lib/sinatra/lazy_auth.rb +56 -0
  53. data/lib/sinatra/rabbit.rb +278 -0
  54. data/lib/sinatra/respond_to.rb +272 -0
  55. data/lib/sinatra/sequel_transaction.rb +27 -0
  56. data/lib/sinatra/static_assets.rb +83 -0
  57. data/lib/sinatra/url_for.rb +44 -0
  58. metadata +270 -0
@@ -0,0 +1,55 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Dcmgr
4
+ module Endpoints
5
+ def self.define_error(class_name, status_code, &blk)
6
+ c = Class.new(APIError)
7
+ c.status_code(status_code)
8
+ c.instance_eval(&blk) if blk
9
+ self.const_set(class_name.to_sym, c)
10
+ end
11
+
12
+ class APIError < StandardError
13
+ def self.status_code(code=nil)
14
+ if code
15
+ @status_code = code
16
+ end
17
+ @status_code || raise("@status_code for the class is not set")
18
+ end
19
+
20
+ def status_code
21
+ self.class.status_code
22
+ end
23
+ end
24
+
25
+ define_error(:UnknownUUIDResource, 404)
26
+ define_error(:UnknownMember, 400)
27
+ define_error(:InvalidCredentialHeaders, 400)
28
+ define_error(:InvalidRequestCredentials, 400)
29
+ define_error(:DisabledAccount, 403)
30
+ define_error(:OperationNotPermitted, 403)
31
+ define_error(:UndefinedVolumeSize, 400)
32
+ define_error(:StoragePoolNotPermitted, 403)
33
+ define_error(:UnknownStoragePool, 404)
34
+ define_error(:OutOfDiskSpace, 400)
35
+ define_error(:DatabaseError, 400)
36
+ define_error(:UndefinedVolumeID, 400)
37
+ define_error(:InvalidDeleteRequest, 400)
38
+ define_error(:UnknownVolume, 404)
39
+ define_error(:UnknownHostPool, 404)
40
+ define_error(:UnknownInstance, 404)
41
+ define_error(:UndefindVolumeSnapshotID, 400)
42
+ define_error(:UnknownVolumeSnapshot, 404)
43
+ define_error(:UndefinedRequiredParameter, 400)
44
+ define_error(:InvalidVolumeSize, 400)
45
+ define_error(:OutOfHostCapacity, 400)
46
+ define_error(:UnknownSshKeyPair, 404)
47
+ define_error(:UndefinedStoragePoolID, 400)
48
+
49
+ # netfilter_group
50
+ define_error(:UndefinedNetfilterGroup, 400)
51
+ define_error(:UnknownNetfilterGroup, 400)
52
+ define_error(:NetfilterGroupNotPermitted, 400)
53
+ define_error(:DuplicatedNetfilterGroup, 400)
54
+ end
55
+ end
@@ -0,0 +1,129 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'extlib'
4
+ require 'sinatra/base'
5
+ require 'yaml'
6
+ require 'json'
7
+
8
+ require 'dcmgr'
9
+
10
+ # Metadata service endpoint for running VMs.
11
+ # The running VM can not identify itself that who or where i am. The service supplies these information from somewhere
12
+ # out of the VM. It publishes some very crucial information to each VM so that the access control to this service is
13
+ # mandated at both levels, the network and the application itself.
14
+ #
15
+ # The concept of the service is similar with Amazon EC2's Metadata service given via http://169.254.169.254/. The
16
+ # difference is the URI structure. This gives the single point URI as per below:
17
+ # http://metadata.server/[version]/meatadata.[format]
18
+ # It will return a document which results in a syntax specified in the last extension field. The document contains
19
+ # over all information that the VM needs for self recoginition.
20
+ module Dcmgr
21
+ module Endpoints
22
+ class Metadata < Sinatra::Base
23
+ include Dcmgr::Logger
24
+
25
+ disable :sessions
26
+ disable :show_exceptions
27
+
28
+ LATEST_PROVIDER_VER_ID='2010-11-01'
29
+
30
+ get '/' do
31
+ ''
32
+ end
33
+
34
+ get '/:version/metadata.*' do
35
+ #get %r!\A/(\d{4}-\d{2}-\d{2})/metadata.(\w+)\Z! do
36
+ v = params[:version]
37
+ ext = params[:splat][0]
38
+ v = case v
39
+ when 'latest'
40
+ LATEST_PROVIDER_VER_ID
41
+ when /\A\d{4}-\d{2}-\d{2}\Z/
42
+ v
43
+ else
44
+ raise "Invalid syntax in the version"
45
+ end
46
+ v = v.gsub(/-/, '')
47
+
48
+ hash_doc = begin
49
+ self.class.find_const("Provider_#{v}").new.document(request.ip)
50
+ rescue NameError => e
51
+ raise e if e.is_a? NoMethodError
52
+ logger.error("ERROR: Unsupported metadata version: #{v}")
53
+ logger.error(e)
54
+ error(404, "Unsupported metadata version: #{v}")
55
+ rescue UnknownSourceIpError => e
56
+ error(404, "Unknown source IP: #{e.message}")
57
+ end
58
+
59
+ return case ext
60
+ when 'json'
61
+ JSON.dump(hash_doc)
62
+ when 'sh'
63
+ shell_dump(hash_doc)
64
+ when 'yaml'
65
+ YAML.dump(hash_doc)
66
+ else
67
+ raise "Unsupported format: .#{ext}"
68
+ end
69
+ end
70
+
71
+ private
72
+ def shell_dump(hash)
73
+ # TODO: values to be shell escaped
74
+ hash.map {|k,v|
75
+ "#{k.to_s.upcase}='#{v}'"
76
+ }.join("\n")
77
+ end
78
+
79
+ class UnknownSourceIpError < StandardError; end
80
+
81
+ # Base class for Metadata provider
82
+ class Provider
83
+ # Each Metadata provider returns a Hash data for a VM along with the src_ip
84
+ # @param [String] src_ip Source IP address who requested to the Meatadata service.
85
+ # @return [Hash] Details for the VM
86
+ def document(src_ip)
87
+ raise NotImplementedError
88
+ end
89
+ end
90
+
91
+ # 2010-11-01 version of metadata provider
92
+ class Provider_20101101 < Provider
93
+ # {:cpu_cores=>1,
94
+ # :memory_size=>100,
95
+ # :state=>'running',
96
+ # :user_data=>'......',
97
+ # :network => [{
98
+ # :ip=>'192.168.1.1',
99
+ # :name=>'xxxxxx'
100
+ # }]
101
+ # }
102
+ def document(src_ip)
103
+ ip = Models::IpLease.find(:ipv4=>src_ip)
104
+ if ip.nil? || ip.instance_nic.nil?
105
+ raise UnknownSourceIpError, src_ip
106
+ end
107
+ inst = ip.instance_nic.instance
108
+ ret = {
109
+ :instance_id=>inst.canonical_uuid,
110
+ :cpu_cores=>inst.cpu_cores,
111
+ :memory_size=>inst.memory_size,
112
+ :state => inst.state,
113
+ :user_data=>inst.user_data.to_s,
114
+ }
115
+ # IP/network values
116
+ ret[:network] = inst.nic.map { |nic|
117
+ {:ip=>nic.ip.ipv4,
118
+ :name=>nic.ip.network.name,
119
+ }
120
+ }
121
+ ret[:volume] = inst.volume.map { |v|
122
+ }
123
+ ret
124
+ end
125
+ end
126
+
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'logger'
3
+
4
+ module Dcmgr
5
+ module Logger
6
+
7
+ @logdev = ::Logger::LogDevice.new(STDOUT)
8
+
9
+ def self.default_logdev
10
+ @logdev
11
+ end
12
+
13
+ # Factory method for ::Logger
14
+ def self.create(name=nil)
15
+ l = ::Logger.new(default_logdev)
16
+ l.progname = name
17
+ l
18
+ end
19
+
20
+ def self.included(klass)
21
+ klass.class_eval {
22
+
23
+ @class_logger = Logger.create(self.to_s.split('::').last)
24
+
25
+ def self.logger
26
+ @class_logger
27
+ end
28
+
29
+ def logger
30
+ self.class.logger
31
+ end
32
+
33
+ def self.logger_name
34
+ @class_logger.progname
35
+ end
36
+
37
+ def self.logger_name=(name)
38
+ @class_logger.progname = name
39
+ end
40
+ }
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,104 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Dcmgr::Models
4
+ class Account < BaseNew
5
+ taggable 'a'
6
+ with_timestamps
7
+ plugin :single_table_inheritance, :uuid, :model_map=>{}
8
+ plugin :subclasses
9
+
10
+ # pk has to be overwritten by the STI subclasses.
11
+ unrestrict_primary_key
12
+
13
+ DISABLED=0
14
+ ENABLED=1
15
+
16
+ inheritable_schema do
17
+ String :description, :size=>100
18
+ Fixnum :enabled, :default=>ENABLED, :null=>false
19
+ end
20
+
21
+ one_to_many :tags
22
+
23
+ def disable?
24
+ self.enabled == DISABLED
25
+ end
26
+
27
+ def enable?
28
+ self.enabled == ENABLED
29
+ end
30
+
31
+ def to_hash_document
32
+ h = self.values.dup
33
+ h[:id] = h[:uuid] = self.canonical_uuid
34
+ h
35
+ end
36
+
37
+
38
+ # STI class variable setter, getter methods.
39
+ class << self
40
+ def default_values
41
+ @default_values ||= {}
42
+ end
43
+
44
+ def pk(pk=nil)
45
+ if pk
46
+ default_values[:id] = pk
47
+ end
48
+ default_values[:id]
49
+ end
50
+
51
+ def uuid(uuid=nil)
52
+ if uuid.is_a?(String)
53
+ uuid = uuid.downcase
54
+ if uuid !~ /^[a-z0-9]{8}$/
55
+ raise "Invalid syntax of uuid: #{uuid}"
56
+ end
57
+ default_values[:uuid] = uuid
58
+ end
59
+ default_values[:uuid] || raise("#{self}.uuid is unset. Set the unique number")
60
+ end
61
+
62
+ def description(description=nil)
63
+ if description
64
+ default_values[:description] = description
65
+ end
66
+ default_values[:description]
67
+ end
68
+ end
69
+
70
+ module SystemAccount
71
+ def self.define_account(class_name, &blk)
72
+ unless class_name.is_a?(Symbol) || class_name.is_a?(String)
73
+ raise ArgumentError
74
+ end
75
+
76
+ c = Class.new(Account, &blk)
77
+ self.const_set(class_name.to_sym, c)
78
+ Account.sti_model_map[c.uuid] = c
79
+ Account.sti_key_map[c.to_s] = c.uuid
80
+ c
81
+ end
82
+ end
83
+
84
+ install_data_hooks do
85
+ Account.subclasses.each { |m|
86
+ Account.create(m.default_values.dup)
87
+ }
88
+ end
89
+
90
+ SystemAccount.define_account(:DatacenterAccount) do
91
+ pk 100
92
+ uuid '00000000'
93
+ description 'datacenter system account'
94
+
95
+ # DatacenterAccount never be disabled
96
+ def before_save
97
+ super
98
+ self.enabled = Account::ENABLED
99
+ end
100
+ end
101
+
102
+ end
103
+ end
104
+
@@ -0,0 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Dcmgr::Models
4
+ # Base class for the model class which belongs to a specific account.
5
+ class AccountResource < BaseNew
6
+
7
+ inheritable_schema do
8
+ String :account_id, :null=>false, :index=>true
9
+ end
10
+
11
+ def account
12
+ Account[self.account_id]
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,69 @@
1
+ require 'sequel'
2
+
3
+ module Dcmgr
4
+ module Models
5
+ class InvalidUUIDError < StandardError; end
6
+ class DuplicateUUIDError < StandardError; end
7
+
8
+ module UUIDMethods
9
+ module ClassMethods
10
+ # override [] method. add search by uuid String
11
+ def [](*args)
12
+ if args.size == 1 and args[0].is_a? String
13
+ super(:uuid=>trim_uuid(args[0]))
14
+ else
15
+ super(*args)
16
+ end
17
+ end
18
+
19
+ def trim_uuid(p_uuid)
20
+ if p_uuid and p_uuid.length == self.prefix_uuid.length + 9
21
+ return p_uuid[(self.prefix_uuid.length+1), p_uuid.length]
22
+ end
23
+ raise InvalidUUIDError, "invalid uuid: #{p_uuid}"
24
+ end
25
+
26
+ def set_prefix_uuid(prefix_uuid)
27
+ @prefix_uuid = prefix_uuid
28
+ end
29
+
30
+ attr_reader :prefix_uuid
31
+ end
32
+
33
+ def generate_uuid
34
+ "%08x" % rand(16 ** 8)
35
+ end
36
+
37
+ def setup_uuid
38
+ self.uuid = generate_uuid unless self.values[:uuid]
39
+ end
40
+
41
+ def before_create
42
+ setup_uuid
43
+ end
44
+
45
+ def save(*columns)
46
+ super
47
+ rescue Sequel::DatabaseError => e
48
+ Dcmgr.logger.info "db error: %s" % e
49
+ Dcmgr.logger.info " " + e.backtrace.join("\n ")
50
+
51
+ raise DuplicateUUIDError if /^Mysql::Error: Duplicate/ =~ e.message
52
+ raise e
53
+ end
54
+
55
+ def uuid
56
+ "%s-%s" % [self.class.prefix_uuid, self.values[:uuid]]
57
+ end
58
+
59
+ def to_s
60
+ uuid
61
+ end
62
+ end
63
+
64
+ class Base < Sequel::Model
65
+ include UUIDMethods
66
+ extend UUIDMethods::ClassMethods
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,371 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'sequel/model'
4
+
5
+
6
+ module Dcmgr::Models
7
+ class InvalidUUIDError < StandardError; end
8
+ class UUIDPrefixDuplication < StandardError; end
9
+
10
+ # Sequal::Model plugin to inject the Taggable feature to the model
11
+ # class.
12
+ #
13
+ # Taggable model supports the features below:
14
+ # - Taggable.uuid_prefix to both set and get uuid_prefix for the model.
15
+ # - Collision detection for specified uuid_prefix.
16
+ # - Generate unique value for :uuid column at initialization.
17
+ # - Add column :uuid if the model is capable of :schema plugin methods.
18
+ module Taggable
19
+ UUID_TABLE='abcdefghijklmnopqrstuvwxyz0123456789'.split('').freeze
20
+ UUID_REGEX=%r/^(\w+)-([#{UUID_TABLE.join}]+)/
21
+
22
+ def self.uuid_prefix_collection
23
+ @uuid_prefix_collection ||= {}
24
+ end
25
+
26
+ # Find a taggable model object from the
27
+ # given canonical uuid.
28
+ #
29
+ # # Find an account.
30
+ # Taggble.find('a-xxxxxxxx')
31
+ #
32
+ # # Find a user.
33
+ # Taggble.find('u-xxxxxxxx')
34
+ def self.find(uuid)
35
+ raise ArgumentError, "Invalid uuid syntax: #{uuid}" unless uuid =~ UUID_REGEX
36
+ upc = uuid_prefix_collection[$1.downcase]
37
+ raise "Unknown uuid prefix: #{$1.downcase}" if upc.nil?
38
+ upc[:class].find(:uuid=>$2)
39
+ end
40
+
41
+ # Checks if the uuid object stored in the database.
42
+ def self.exists?(uuid)
43
+ !find(uuid).nil?
44
+ end
45
+
46
+ def self.configure(model)
47
+ model.schema_builders << proc {
48
+ unless has_column?(:uuid)
49
+ # add :uuid column with unique index constraint.
50
+ column(:uuid, String, :size=>8, :null=>false, :fixed=>true, :unique=>true)
51
+ end
52
+ }
53
+ end
54
+
55
+ module InstanceMethods
56
+ # read-only instance method to retrieve @uuid_prefix class
57
+ # variable.
58
+ def uuid_prefix
59
+ self.class.uuid_prefix
60
+ end
61
+
62
+ def after_initialize
63
+ super
64
+ # set random generated uuid value
65
+ self[:uuid] ||= Array.new(8) do UUID_TABLE[rand(UUID_TABLE.size)]; end.join
66
+ end
67
+
68
+ # Returns canonicalized uuid which has the form of
69
+ # "{uuid_prefix}-{uuid}".
70
+ def canonical_uuid
71
+ "#{self.uuid_prefix}-#{self[:uuid]}"
72
+ end
73
+ alias_method :cuuid, :canonical_uuid
74
+
75
+ # Put the tag on the object.
76
+ #
77
+ # This method just delegates the method call of Tag#label().
78
+ # @params [Models::Tag,String] tag_or_tag_uuid 'tag-xxxx' is expected when the type is string
79
+ def label_tag(tag_or_tag_uuid)
80
+ tag = case tag_or_tag_uuid
81
+ when String
82
+ Tag[tag_or_tag_uuid]
83
+ when Tag
84
+ tag_or_tag_uuid
85
+ else
86
+ raise ArgumentError, "Invalid type: #{tag_or_tag_uuid.class}"
87
+ end
88
+
89
+ tag.label(self.uuid)
90
+ end
91
+
92
+ # Remove the labeled tag from the object
93
+ #
94
+ # This method just delegates the method call of Tag#unlabel().
95
+ # @params [Models::Tag,String] tag_or_tag_uuid 'tag-xxxx' is expected when the type is string
96
+ def unlabel_tag(tag_or_tag_uuid)
97
+ tag = case tag_or_tag_uuid
98
+ when String
99
+ Tag[tag_or_tag_uuid]
100
+ when Tag
101
+ tag_or_tag_uuid
102
+ else
103
+ raise ArgumentError, "Invalid type: #{tag_or_tag_uuid.class}"
104
+ end
105
+
106
+ tag.unlabel(self.uuid)
107
+ end
108
+
109
+ def to_hash()
110
+ self.values.dup.merge({:id=>canonical_uuid, :uuid=>canonical_uuid})
111
+ end
112
+ end
113
+
114
+ module ClassMethods
115
+ # Getter and setter for uuid_prefix of the class.
116
+ #
117
+ # @example
118
+ # class Model1 < Sequel::Model
119
+ # plugin Taggable
120
+ # uuid_prefix('m')
121
+ # end
122
+ #
123
+ # Model1.uuid_prefix # == 'm'
124
+ # Model1.new.canonical_uuid # == 'm-abcd1234'
125
+ def uuid_prefix(prefix=nil)
126
+ if prefix
127
+ raise UUIDPrefixDuplication, "Found collision for uuid_prefix key: #{prefix}" if Taggable.uuid_prefix_collection.has_key?(prefix)
128
+
129
+ Taggable.uuid_prefix_collection[prefix]={:class=>self}
130
+ @uuid_prefix = prefix
131
+ end
132
+
133
+ @uuid_prefix || (superclass.uuid_prefix if superclass.respond_to?(:uuid_prefix)) || raise("uuid prefix is unset for:#{self}")
134
+ end
135
+
136
+
137
+ # Override Model.[] to add lookup by uuid.
138
+ #
139
+ # @example
140
+ # Account['a-xxxxxx']
141
+ def [](*args)
142
+ if args.size == 1 and args[0].is_a? String
143
+ super(:uuid=>trim_uuid(args[0]))
144
+ else
145
+ super(*args)
146
+ end
147
+ end
148
+
149
+ # Returns the uuid string which is removed prefix part: /^(:?\w+)-/.
150
+ #
151
+ # @example
152
+ # Account.trim_uuid('a-abcd1234') # = 'abcd1234'
153
+ # @example Will get InvalidUUIDError as the uuid with invalid prefix has been tried.
154
+ # Account.trim_uuid('u-abcd1234') # 'u-' prefix is for User model.
155
+ def trim_uuid(p_uuid)
156
+ regex = %r/^#{self.uuid_prefix}-/
157
+ if p_uuid and p_uuid =~ regex
158
+ return p_uuid.sub(regex, '')
159
+ end
160
+ raise InvalidUUIDError, "Invalid uuid or unsupported uuid: #{p_uuid} in #{self}"
161
+ end
162
+
163
+ # Checks the uuid syntax if it is for the Taggable class.
164
+ def check_uuid_format(uuid)
165
+ uuid =~ /^#{self.uuid_prefix}-/
166
+ end
167
+ end
168
+
169
+ end
170
+
171
+ # Sequel::Model plugin extends :schema plugin to merge the column
172
+ # definitions in its parent class.
173
+ #
174
+ # class Model1 < Sequel::Model
175
+ # plugin InheritableSchema
176
+ #
177
+ # inheritable_schema do
178
+ # String :col1
179
+ # end
180
+ # end
181
+ #
182
+ # class Model2 < Model1
183
+ # inheritable_schema do
184
+ # String :col2
185
+ # end
186
+ # end
187
+ #
188
+ # Model2.create_table!
189
+ #
190
+ # Then the schema for Model2 becomes as follows:
191
+ # primary_key :id, :type=>Integer, :unsigned=>true
192
+ # String :col1
193
+ # String :col2
194
+ module InheritableSchema
195
+
196
+ module ClassMethods
197
+ # Creates table, using the column information from set_schema.
198
+ def create_table
199
+ db.create_table(table_name, :generator=>schema)
200
+ @db_schema = get_db_schema(true)
201
+ columns
202
+ end
203
+
204
+ # Drops the table if it exists and then runs
205
+ # create_table. Should probably
206
+ # not be used except in testing.
207
+ def create_table!
208
+ drop_table rescue nil
209
+ create_table
210
+ end
211
+
212
+ # Creates the table unless the table already exists
213
+ def create_table?
214
+ create_table unless table_exists?
215
+ end
216
+
217
+ # Drops table.
218
+ def drop_table
219
+ db.drop_table(table_name)
220
+ end
221
+
222
+ # Returns true if table exists, false otherwise.
223
+ def table_exists?
224
+ db.table_exists?(table_name)
225
+ end
226
+
227
+ def schema
228
+ builders = []
229
+ c = self
230
+ begin
231
+ builders << c.schema_builders if c.respond_to?(:schema_builders)
232
+ end while((c = c.superclass) && c != Sequel::Model)
233
+
234
+ builders = builders.reverse.flatten
235
+ builders.delete(nil)
236
+
237
+ schema = Sequel::Schema::Generator.new(db) {
238
+ primary_key :id, Integer, :null=>false, :unsigned=>true
239
+ }
240
+ builders.each { |blk|
241
+ schema.instance_eval(&blk)
242
+ }
243
+ set_primary_key(schema.primary_key_name) if schema.primary_key_name
244
+
245
+ schema
246
+ end
247
+
248
+ def schema_builders
249
+ @schema_builders ||= []
250
+ end
251
+
252
+ def inheritable_schema(name=nil, &blk)
253
+ set_dataset(db[name || implicit_table_name])
254
+ self.schema_builders << blk
255
+ end
256
+ end
257
+
258
+ end
259
+
260
+
261
+ class BaseNew < Sequel::Model
262
+
263
+ LOCK_TABLES_KEY='__locked_tables'
264
+
265
+ def self.lock!
266
+ locktbls = Thread.current[LOCK_TABLES_KEY]
267
+ if locktbls
268
+ locktbls[self.db.uri.to_s + @dataset.first_source_alias.to_s]=1
269
+ end
270
+ end
271
+
272
+ def self.unlock!
273
+ locktbls = Thread.current[LOCK_TABLES_KEY]
274
+ if locktbls
275
+ locktbls.delete(self.db.uri.to_s + @dataset.first_source_alias.to_s)
276
+ end
277
+ end
278
+
279
+ def self.dataset
280
+ locktbls = Thread.current[LOCK_TABLES_KEY]
281
+ if locktbls && locktbls[self.db.uri.to_s + @dataset.first_source_alias.to_s]
282
+ @dataset.opts = @dataset.opts.merge({:lock=>:update})
283
+ else
284
+ @dataset.opts = @dataset.opts.merge({:lock=>nil})
285
+ end
286
+ @dataset
287
+ end
288
+
289
+
290
+
291
+ def self.Proxy(klass)
292
+ colnames = klass.schema.columns.map {|i| i[:name] }
293
+ colnames.delete_if(klass.primary_key) if klass.restrict_primary_key?
294
+ s = ::Struct.new(*colnames) do
295
+ def to_hash
296
+ n = {}
297
+ self.each_pair { |k,v|
298
+ n[k.to_sym]=v
299
+ }
300
+ n
301
+ end
302
+ end
303
+ s
304
+ end
305
+
306
+
307
+ # Callback when the initial data is setup to the database.
308
+ def self.install_data
309
+ install_data_hooks.each{|h| h.call }
310
+ end
311
+
312
+ # Add callbacks to setup the initial data. The hooks will be
313
+ # called when Model1.install_data() is called.
314
+ #
315
+ # class Model1 < Base
316
+ # install_data_hooks do
317
+ # Model1.create({:col1=>1, :col2=>2})
318
+ # end
319
+ # end
320
+ def self.install_data_hooks(&blk)
321
+ @install_data_hooks ||= []
322
+ if blk
323
+ @install_data_hooks << blk
324
+ end
325
+ @install_data_hooks
326
+ end
327
+
328
+
329
+ private
330
+ def self.inherited(klass)
331
+ super
332
+
333
+ klass.plugin InheritableSchema
334
+ klass.class_eval {
335
+
336
+ # Add timestamp columns and set callbacks using Timestamps
337
+ # plugin.
338
+ #
339
+ # class Model1 < Base
340
+ # with_timestamps
341
+ # end
342
+ def self.with_timestamps
343
+ self.schema_builders << proc {
344
+ unless has_column?(:created_at)
345
+ column(:created_at, Time, :null=>false)
346
+ end
347
+ unless has_column?(:updated_at)
348
+ column(:updated_at, Time, :null=>false)
349
+ end
350
+ }
351
+
352
+ self.plugin :timestamps, :update_on_create=>true
353
+ end
354
+
355
+ # Install Taggable module as Sequel plugin and set uuid_prefix.
356
+ #
357
+ # class Model1 < Base
358
+ # taggable 'm'
359
+ # end
360
+ def self.taggable(uuid_prefix)
361
+ return if self == BaseNew
362
+ self.plugin Taggable
363
+ self.uuid_prefix(uuid_prefix)
364
+ end
365
+
366
+ }
367
+
368
+ end
369
+
370
+ end
371
+ end