wakame-vdc-agents 10.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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