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.
- data/LICENSE +202 -0
- data/NOTICE +1 -0
- data/Rakefile +142 -0
- data/bin/hva +972 -0
- data/bin/nsa +147 -0
- data/bin/sta +182 -0
- data/config/hva.conf.example +10 -0
- data/config/initializers/isono.rb +43 -0
- data/config/initializers/passenger.rb +6 -0
- data/config/initializers/sequel.rb +21 -0
- data/config/nsa.conf.example +9 -0
- data/config/path_resolver.rb +12 -0
- data/lib/dcmgr.rb +115 -0
- data/lib/dcmgr/endpoints/core_api.rb +1004 -0
- data/lib/dcmgr/endpoints/core_api_mock.rb +816 -0
- data/lib/dcmgr/endpoints/errors.rb +55 -0
- data/lib/dcmgr/endpoints/metadata.rb +129 -0
- data/lib/dcmgr/logger.rb +44 -0
- data/lib/dcmgr/models/account.rb +104 -0
- data/lib/dcmgr/models/account_resource.rb +16 -0
- data/lib/dcmgr/models/base.rb +69 -0
- data/lib/dcmgr/models/base_new.rb +371 -0
- data/lib/dcmgr/models/frontend_system.rb +38 -0
- data/lib/dcmgr/models/host_pool.rb +102 -0
- data/lib/dcmgr/models/image.rb +46 -0
- data/lib/dcmgr/models/instance.rb +255 -0
- data/lib/dcmgr/models/instance_netfilter_group.rb +16 -0
- data/lib/dcmgr/models/instance_nic.rb +68 -0
- data/lib/dcmgr/models/instance_spec.rb +21 -0
- data/lib/dcmgr/models/ip_lease.rb +42 -0
- data/lib/dcmgr/models/netfilter_group.rb +88 -0
- data/lib/dcmgr/models/netfilter_rule.rb +21 -0
- data/lib/dcmgr/models/network.rb +32 -0
- data/lib/dcmgr/models/physical_host.rb +67 -0
- data/lib/dcmgr/models/request_log.rb +25 -0
- data/lib/dcmgr/models/ssh_key_pair.rb +55 -0
- data/lib/dcmgr/models/storage_pool.rb +134 -0
- data/lib/dcmgr/models/tag.rb +126 -0
- data/lib/dcmgr/models/tag_mapping.rb +28 -0
- data/lib/dcmgr/models/volume.rb +130 -0
- data/lib/dcmgr/models/volume_snapshot.rb +47 -0
- data/lib/dcmgr/node_modules/hva_collector.rb +134 -0
- data/lib/dcmgr/node_modules/sta_collector.rb +72 -0
- data/lib/dcmgr/scheduler.rb +12 -0
- data/lib/dcmgr/scheduler/find_last.rb +16 -0
- data/lib/dcmgr/scheduler/find_random.rb +16 -0
- data/lib/dcmgr/stm/instance.rb +25 -0
- data/lib/dcmgr/stm/snapshot_context.rb +33 -0
- data/lib/dcmgr/stm/volume_context.rb +65 -0
- data/lib/dcmgr/web/base.rb +21 -0
- data/lib/sinatra/accept_media_types.rb +128 -0
- data/lib/sinatra/lazy_auth.rb +56 -0
- data/lib/sinatra/rabbit.rb +278 -0
- data/lib/sinatra/respond_to.rb +272 -0
- data/lib/sinatra/sequel_transaction.rb +27 -0
- data/lib/sinatra/static_assets.rb +83 -0
- data/lib/sinatra/url_for.rb +44 -0
- 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
|
data/lib/dcmgr/logger.rb
ADDED
@@ -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
|