wakame-vdc-agents 10.11.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|