Cloud Foundry Service Node源码分析及实现【附下载】
来源:互联网 发布:cn域名需要备案吗 编辑:程序博客网 时间:2024/06/05 04:46
从Word拷贝过来结果好多都变形了,附加下载地址:http://download.csdn.net/detail/wearenoth/5060429
Cloud Foundry Services源码分析之Node
引言
Service结构
在Cloud Foundry中Service的结构不是太复杂,由两个组件组成——Gateway、Node。如图1展示了Service相关的几个主要组件,每个组件有十分明确的分工:
图 1 Service相关主要组件
Gateway
其它组件(Cloud Controller)访问Node的入口,它对外提供了对Node进行管理的一套“接口”。同时它对外隐藏内部Node的结构,这样外部的组件就可以忽略内部Node的情况,只需要关心Service实例的创建、绑定的动作。
Node
负责管理Service,包括创建(provision)、注销(unprovision)、绑定(bind)、启用(enable)、禁用(disabled)等操作。Node不是Service的提供者,它是本地Service的管理者。
NATS
最底层的消息总线,各大组件间的通信都由它进行转发。是一个P/S结构的消息总线,所以在源码中会经常见到调用subscribe、publish方法。
DEA
APP运行的容器,Service就是为APP提供服务。在源码中会见到Node中提供bind方法,就是为了将创建的Service实例与APP绑定,APP能够访问Service实例。
Cloud Controller
Cloud Foundry最核心的控制大脑。它接受各个组件和Client的请求进行处理,然后通过NATS向相应的组件发送指令,要求其执行。在源码中Node接收到如provision这样的请求,这些请求是由Cloud Controller发送给Gateway,Gateway经过负载均衡后再向Node发出。
类图
本文主要介绍Cloud Foundry中Node的实现,图2绘制了CloudFoundry中Services中Node点的类图。
注意:Echo::XXX并不属于CloudFoundry中的内容,而是一个简单的Service的Node实现,也是本文中主要进行分析的Service实现。添加Echo Service只要将自定义的Echo::NodeBin通过配置到启动文件中,就可以在启动Cloud Foundry的时候自启动自定义Service。
整个Service Node分为2个层次结构,一部分是CloudFoundry提供的Base模板,如类图中的Base::XXX部分,这部分已经帮助Service开发人员完成了大部分Service的开发工作;另一部分是XXX Service部分,这部分就是Service开发人员所需要进行编写的代码,如类图中的Echo::XXX。
图中每个类都有其明确的职责,在下文其余部分会一点点进行分析。
图2 Echo Node类图
类功能
图2中类图分为两个层次。其中Base下的类都是系统提供的ServiceNode模板,也是下文中重点分析对象;自定义Service就是通过继承NodeBin和Node两个类,然后实现模板中预留的接口,在最后我们将会详细介绍如何去编写这部分的代码,这也是Service开发人员需要完成的部分。表1介绍了每个类实现的功能。
表 1 类功能说明
类名
功能
Base::Base
提供了整个Service通信框架,主要是提供了NATS的连接与初始化。
Base::Node
实现了Service Node功能模板,预留了大量的抽象方法便于Service开发者实现自定义的功能。
Base::NodeBin
主要完成Service Node的初始化配置,然后将配置参数传入Base::Node。
Echo::NodeBin
需要实现Base::NodeBin下的两个指明调用的Node点以及配置文件的方法即可。
Echo::Node
需要实现Base::Node下的抽象方法,实现Service Node的实例创建、管理、注销、重启等操作。
第一部分:源码分析
源码之间的关系错综复杂,很多地方都有一种剪不断理还乱的感觉,所以在阅读该部分内容时,建议:
² 一边阅读源码,一边阅读本文。文中引用代码都给出了“路径名 #方法名”的格式。路径名是Cloud Foundry在GitHub上面的路径,源码直接到https://github.com/cloudfoundry上查阅。
² 文中引用代码都尽量截取了我感兴趣的部分,这部分会以灰色背景标识;此外一些地方代码被稍微修改。
² 在讲述某个方面内容时,为保证内容尽可能不跑题,所以相关的内容都会给出其可以参考的章节,这些地方都会以【XXX】的方式注明。如果看不懂了,可以跳跃到相应内容查看。
² 如果还看不懂,建议上网查阅相应资料。
² 文中不免有大量表述不清晰、谬误之处,欢迎发送邮件webothh@126.com。
文中主要以Echo Service为例,其他的Service也是一样的道理。
启动流程
图2-1 Node启动流程
以Echo Service为例,图2-1描绘了Node的启动流程。Service中Node的启动过程其实不是太复杂,真正复杂的是配置参数与相应主题的功能。
注意:步骤中创建Echo::XXX实例不能算作启动流程的主要部分,它们仅仅只是调用了各自的new方法而已,描述它们是为了标识流程的执行方向才加入说明。
Service Node的启动入口位于自定义Service目录中bin目录下对应ServiceNode的可执行文件中,如代码2.1所示,启动Service Node代码看似简单,实际它后续的工作却是很复杂。
代码2.1:vcap-services / echo /bin /echo_node
VCAP::Services::Echo::NodeBin.new.start
创建Echo::NodeBin实例
因为Echo::NodeBin继承自Base::NodeBin,所以它也继承了Base::NodeBin所有的功能。创建Echo::NodeBin实例的过程就在代码2.1中——调用new方法创建Echo::NodeBin实例。创建后的实例调用的start方法是在Base::NodeBin方法中实现。start方法中就包含了启动流程中后续的所有过程。
注意:Service开发者不需要去重新实现该方法,启动过程中需要附加的一些功能,Cloud Foundry提供了2个Hook方法给Service开发者。分别为【pre_send_announcement】方法和【additional_config】方法。
初始化参数配置
Echo::NodeBin调用start方法定义在Base::NodeBin中,执行的第一个步骤就是进行所有参数的初始化。初始化参数配置代码过程很简单,就是确定配置文件->载入配置文件->初始化参数列表。
如代码2.2所示:首先需要确定默认的配置文件,对于default_config_file方法的说明可以查看【default_config_file】。OptionParser部分的内容一般不会得到执行,如果用户指定了启动过程中的配置文件,则会使用新的配置文件,否则使用默认配置文件。当然一般情况下这个opt参数是没有进行指定的,如果需要指定,可以对启动脚本进行修改。
代码2.2:vcap-services-base / lib /base /node_bin.rb #start
def start
config_file = default_config_file
OptionParser.newdo |opts|
opts.banner ="Usage: #{$0.split(/\//)[-1]}[options]"
opts.on("-c","--config [ARG]","Configuration File")do |opt|
config_file= opt
end
……
然后载入配置文件,如代码2.3所示。配置文件采用YAML格式存储,最后得到的config中存储的是一个Hash表。
代码2.3:vcap-services-base / lib /base /node_bin.rb #start
config = YAML.load_file(config_file)
最后进行参数配置,如代码2.4所示。需要配置的参数内容非常复杂,参数的配置主要分为4个部分,除去Node需要的基本配置参数外,还包含有Warden的配置参数,日志文件的配置参数以及附加参数,详细部分参看【配置参数】。
代码2.4:vcap-services-base / lib /base /node_bin.rb #start
options = {
:index=> parse_property(config, "index",Integer, :optional => true),
……
# Wardenized service configuration
:base_dir=> parse_property(config, "base_dir",String, :optional => true),
……
}
# Workaround for services that support running the service both inside and outside warden
use_warden = parse_property(config,"use_warden",Boolean, :optional => true, :default=> false)
if use_warden
warden_config = parse_property(config,"warden", Hash, :optional=> true)
……
options[:port_range]= parse_property(warden_config, "port_range",Range)
……
end
VCAP::Logging.setup_from_config(config["logging"])
# Use the node id for logger identity name.
options[:logger]= VCAP::Logging.logger(options[:node_id])
@logger= options[:logger]
options = additional_config(options, config)
注意:在代码2.4最后一行表示Service开发者也可以添加自定义的参数。在Base::NodeBin中提供了抽象方法additional_options来方便service开发者添加自己所需要的参数,详细部分参考【additional_config】
创建Echo::Node实例
如代码2.5所示,在Base::NodeBin中start方法的结束部分开始创建一个Echo::Node实例。EM是指的是eventmachine,是一个异步事件处理机,与Node.js类似,更多内容参考【NATS与Event Machine】。
注意:其中node_class方法与default_config_file方法一样需要在Echo::NodeBin中进行重写,详细说明见【node_class】。
代码2.5:vcap-services-base / lib /base /node_bin.rb #start
EM.rundo
node = node_class.new(options)
……
end
end
在Echo Service中node_class方法返回的就是Echo::Node,所以调用new方法创建时调用的就是Echo::Node的initialize方法,如代码2.6所示。在Echo::Node的initialize方法中可以不做其它工作,直接调用父类中的initialize方法即可。
注意:该方法必须使用super调用父类(Base::Node)中的initialize方法。因为订阅主题的主题工作是在Base::Node的on_connect_node方法中进行实现,同时连接到NATS与工作也是在Base::Node的父类(Base::Base)中完成。
代码2.6:vcap-services / echo /lib /echo_service /echo_node.rb #initialize
def initialize(options)
super(options)
end
连接到NATS
连接到NATS是在在Base::Base的initialize方法中完成。但是在调用Base::Base的initialize方法之前,事先调用的是Base::Node中的initialize方法,如代码2.7所示。而在Base::Node中最开始则会调用Base::Base中的initialize方法连接到NATS。
代码2.7:vcap-services-base / lib /base /node.rb #initialize
def initialize(options)
super(options)
连接到NATS看似复杂,但是对于所有的连接到NATS的节点来说,调用流程是一样。如代码2.9所示,调用NATS的connect方法连接到NATS,在其代码块中:
² 向Component注册【--?--】,参考【周期任务】中关于update_varz的说明;
² 调用on_connect_node方法订阅主题,参考【订阅主题】。
代码2.9:vcap-services-base / lib /base /base.rb #initialize
if options[:mbus]
……
@node_nats= NATS.connect(:uri=> options[:mbus])do
status_port = status_user= status_password = nil
if not options[:status].nil?
status_port = options[:status][:port]
status_user = options[:status][:user]
status_password = options[:status][:password]
end
@logger.debug("Registering with NATS")
VCAP::Component.register(:nats=> @node_nats,:type => service_description, :host=> @local_ip,:index => options[:index]|| 0,:config => options, :port=> status_port, :user=> status_user,:password=> status_password)
on_connect_node
end
订阅主题
Base::Base提供了一个连接到NATS的通信框架。订阅主题是在Base::Base的initialize方法中调用on_connect_node方法进行订阅,参见代码2.9中倒数第二行。
注意,不要将on_connect_node理解成是创建与NATS的连接过程,它做的事情是ServiceNode向NATS订阅主题以及添加周期任务(参考【添加周期任务】)。
如代码2.10所示,根据对于该方法的注释我们知道:
² 该方法必须在Base::Node和Base::Provision中进行重写;
² 自定义Services中不能重写该方法。
代码2.10:vcap-services-base / lib /base /base.rb
# Subclasses VCAP::Services::Base::{Node,Provisioner} implement the
# following methods. (Note that actual service Provisioner or Node
# implementations should NOT need to touch these!)
# TODO on_connect_node should be on_connect_nats
abstract :on_connect_node
订阅主题过程在Base::Node的on_connect_node方法中进行实现,如代码2.11所示。这段代码读起来有些拗口,这部分内容将【主题】中展开讨论,这里只需要它订阅了主题即可。
代码2.11:vcap-services-base / lib /base /node.rb #on_connect_node
def on_connect_node
……
%w[provision unprovision bind unbind restore disable_instance enable_instance import_instance update_instance cleanupnfs_instance purge_orphan ].eachdo |op|
eval%[@node_nats.subscribe("#{service_name}.#{op}.#{@node_id}") { |msg, reply| EM.defer{ on_#{op}(msg, reply) } }]
end
%w[discover check_orphan].eachdo |op|
eval%[@node_nats.subscribe("#{service_name}.#{op}") { |msg, reply| EM.defer{ on_#{op}(msg, reply) } }]
end
添加周期任务
在启动流程的最后一部分工作就是添加周期任务,就是Service Node在之后正常运行过程中定期执行的动作。添加的周期任务有两个:
² 向Gateway发送本地相关运行状态,Gateway可以根据这些信息可以更方便的管理多个Service Node,实现Service Node间的均衡。
² 向Component注册信息。
更多关于周期任务的内容参考【周期任务】。
如代码2.12所示,其中一个在on_connect_node方法的最后会设置周期任务,这个周期任务就是实现了向Gateway发送本地相关的运行状态信息。周期任务的执行体全部在send_node_announcement中完成。
代码2.12:vcap-services-base / lib /base /node.rb #on_connect_node
pre_send_announcement
send_node_announcement
EM.add_periodic_timer(30) { send_node_announcement }
end
如代码2.13所示,另外一个则是在Base::Node的initialize方法最后,也就是连接到NATS之后,这个周期任务的执行体则是在update_varz中完成。
代码2.13:vcap-services-base / lib /base /node.rb #initialize
@supported_versions= options[:supported_versions]|| []
z_interval = options[:z_interval]|| 30
EM.add_periodic_timer(z_interval)do
EM.defer { update_varz }
end if @node_nats
小结
该章节内容简要介绍了ServiceNode的启动流程。
² 配置参数的初始化配置时Node启动过程中最为复杂的部分,无论是Service开发人员还是Cloud Foundry管理人员都需要花费大量精力去学习这部分的内容。
² 订阅主题也是Node启动过程中较为复杂的一部分,而到了运行过程中,主题这部分就显得尤为重要。
² 周期任务看似复杂,但是需要Service开发人员注意的地方其实并不多,所以最后我们其实不需要太多的精力去学习它。
² 与NATS的连接也不需要花费太多的心思,它仅仅只是一个消息总线而已,只需要知道如何使用它发送和接收消息即可。
在后面的章节中,我们会分别展开这些部分的内容,一一分析其中的一些机制。
配置
本章节内容主要讲解Service Node中的参数配置,中间会重点介绍几个在下文中经常见到的参数(个人感兴趣)。要弄明白一个参数的作用需要“大胆假设,小心求证”,我没办法顾及所有情况,所以无法保证我下文中所写都是正确的。如果觉得有什么地方有问题,欢迎指正。
default_config_file
在【初始化参数配置】一节中,ServiceNode启动最开始需要指定一个默认配置文件,在代码2.2中使用default_config_file方法来指定这个文件的路径。对于default_config_file方法的定义在Base::NodeBin中,如代码3.1所示。对于abstract的说明参考【abstract】,我们可以将它的作用类比做C++中的纯虚函数,在下文中会多次见到这样的声明。
代码3.1:vcap-services-base / lib /base /node_bin.rb
classVCAP::Services::Base::NodeBin
abstract :default_config_file
对于default_config_file方法说明参考表3-1。
表3-1default_config_file方法说明
函数名:default_config_file
参数名称
说明
输入参数
-
返回值
file path
默认配置文件的路径,例如:File.join(File.dirname(__FILE__), '..', 'config', 'XXX.yml')
如代码3.2所示,在实现EchoService的时候,就需要Echo::NodeBin中进行重写default_config_file方法。重写内容很简单,只需要返回读入的默认文件的路径即可。
代码3.2:vcap-services / echo /bin /echo_node
classVCAP::Services::Echo::NodeBin<VCAP::Services::Base::NodeBin
……
def default_config_file
File.join(File.dirname(__FILE__),'..','config','echo_node.yml')
end
end
注意:Service开发者必须实现该方法,实现格式参考代码3.2即可。
additional_config
在【初始化参数配置】一节中,Service开发者还可以按照自己的意愿添加参数,而对于这些新增参数的初始化过程则是在additional_config方法中进行处理,其定义如代码3.3所示。
代码3.3:vcap-services-base / lib /base /node_bin.rb
classVCAP::Services::Base::NodeBin
abstract:additional_config
在代码中没有指出该方法需要导入的参数类型,返回查看代码2.4就可以知道这个方法需要导入两个参数——options和config。整理后的方法说明如表3-2所示:
表3-2 additional_config方法说明
函数名:additional_config
参数名称
说明
输入参数
options
是一个Hash表,读入的配置参数都会存储在该表中。
config
就是载入的配置文件中的信息内容,它其实就是一个Hash数组,参考【代码2.3】。
返回值
options
更新后的options表,与原来的options相比,新的options加入了在Service开发者需要的参数。
Service开发者只需要在实现该方法时候读入config中新加入的参数即可。例如在Echo Service中,Echo需要使用一个新的参数port表示Service的服务端口号,则如代码3.4所示从config中读入参数即可。
注意:所有的配置参数都在config中存储,最好使用【parse_property】方法从config中读取参数。
代码3.4:vcap-services / echo /bin /echo_node
def additional_config(options, config)
options[:port]= parse_property(config, "port",Integer)
options
end
注意:Service开发者必须实现该方法,该方法存在两个参数options和config,而且要求返回值为更新后的options,实现格式参考代码3.4即可。
parse_property
在Base::NodeBin中将所有经过parse_property方法处理后的参数结果存入在options(一个Hash表)中。这个处理过程的格式如代码3.5所示。
代码3.5:vcap-services-base / lib /base /node_bin.rb
options[:capacity] =parse_property(config,"capacity",Integer,:optional =>true,:default =>200)
Service ServiceNode的配置参数很多,配置文件以YAML格式编写(Cloud Foundry中经常见到YAML与JSON两种数据交换格式,类似于XML),载入配置文件(参考【代码2.3】)后的参数值保存在config变量中。config其实就是一个Hash数组,此时读入的参数还为经过格式转换,在Base::NodeBin中提供了parse_property方法对对参数进行处理。对于parse_property方法说明如表3-3所示。
表3-3parse_property方法说明
函数名:parse_property
参数名称
说明
输入参数
config
就是载入的配置文件Hash数组,整个配置文件内容都在其内部保存。
key
需要查找的参数键值,parse_property方法就是使用config[key]获取到相关参数的值。
type
参数最后的类型,经过parse_property方法,该参数最后会转化为type类型返回。
options
附加选项,包括了:optional与:default两个,详细情况参考图3-1。
返回值
value
经过处理的参数值,类型为type类型。
对于parse_property方法的定义如代码3.6所示。
代码3.6:vcap-services-base / lib /base /node_bin.rb
def parse_property(hash, key, type, options= {})
obj = hash[key]
if obj.nil?
raise "Missing required option: #{key}"unless options[:optional]
options[:default]
elsif type ==Range
raise "Invalid Range object: #{obj}"unless obj.kind_of?(Hash)
first, last = obj["first"], obj["last"]
raise "Invalid Range object: #{obj}"unless first.kind_of?(Integer)and last.kind_of?(Integer)
Range.new(first, last)
else
raise "Invalid #{type}object: #{obj}"unless obj.kind_of?(type)
obj
end
end
代码虽然不长,但是分支情况比较多,对于config中的参数有如下几种处理情况,如图3-1所示。
图3-1 参数配置函数过程
² 如果在config中配置了该参数,并且不是一个Range类型的数据,就会读取该参数的配置,并转化为type类型
² 如果在config中配置了该参数,而且是一个Range类型的数据,返回类型就是一个Range类型。
² 如果在config中没有配置该参数,并且没有加入:optional=true选项,则会报错,表明该参数必须配置
² 如果在config中没有配置该参数,并且加入:optional=true选项,此时如果参数必须有一个默认值,则返回输入参数中的:default值。
² 如果在config中没有配置该参数,并且加入:optional=true选项,此时如果参数不必须有一个默认值,则返回的参数是一个nil值。
配置参数
参数很多很复杂,但不是要求每个参数都需要配置,很多参数其实是可选的,对于一些可选参数还提供了默认值。对于Service Node使用的参数整理后如表3-4、表3-5、表3-6所示。
表3-4整理了配置参数值的情况。
表3-4 全局参数配置表(不完全)
编号
参数名称
options
变量名
类型
可选
默认值
典型值
A01
index
options[:index]
Interger
是
0
A02
plan
options[:plan]
@plan
String
是
free
A03
capacity
options[:capacity]
@capacity
@max_capacity
Interger
是
200
A04
ip_route
options[:ip_route]
String
是
A05
node_id
options[:node_id]
@node_id
options[:logger]
String
否
echo_node_1
A06
z_interval
options[:z_interval]
z_interval
Interger
是
30
A07
mbus
options[:mbus]
@node_nats
String
否
nats://localhost:4222
A08
local_db
options[:local_db]
String
否
sqlite3:/var/vcap/services/echo/echo_node.db
A09
migration_nfs
options[:migration_nfs]
@migration_nfs
String
是
A10
max_nats_payload
options[:max_nats_payload]
Interger
是
A11
fqdn_hosts
options[:fqdn_hosts]
@fqdn_hosts
Boolen
是
FALSE
A12
op_time_limit
options[:op_time_limit]
@op_time_limit
Interger
是
6
A13
supported_version
options[:supported_version]
@supported_versions
Array
否
["1.0"]
A14
default_version
options[:default_version]
String
否
"1.0"
A15
max_clients
options[:max_clients]
Interger
是
A16
database_lock_file
options[:database_lock_file]
String
是
A17
disabled_file
options[:disabled_file]
@disabled_file
String
是
"/var/vcap/store/DISABLED"
A18
options[:logger]
@logger
String
-
logging
否
level: debug
A19
pid
pid_file
String
否
/var/vcap/sys/run/echo_node.pid
A20
base_dir
options[:base_dir]
String
是
A21
service_log_dir
options[:service_log_dir]
String
是
A22
service_common_dir
options[:service_common_dir]
String
是
"/var/vcap/store/common"
A23
service_bin_dir
options[:service_bin_dir]
Hash
是
A24
image_dir
options[:image_dir]
String
是
A25
port_range
options[:port_range]
Range
是
A26
filesystem_quota
options[:filesystem_quota]
Boolen
是
FALSE
A27
service_start_timeout
options[:service_start_timeout]
Interger
是
3
A28
service_status_timeout
options[:service_status_timeout]
Interger
是
3
A29
max_memory
options[:max_memory]
Numeric
是
A30
memory_overhead
options[:memory_overhead]
Numeric
是
0
A31
max_disk
options[:max_disk]
Numeric
是
128
A32
disk_overhead
options[:disk_overhead]
Numeric
是
0
A33
m_interval
options[:m_interval]
Interger
是
10
A34
m_actions
options[:m_actions]
Array
是
[]
A35
m_failed_times
options[:m_failed_times]
Interger
是
3
² 参数名称:配置文件中参数的名称。
² options:经过类型转化后的配置参数保存。
² 变量名:一些参数对应的实例变量。
² 类型:参数类型
² 可选:参数是否可选,是表示该参数可以不配置,否表示该参数必须配置。
² 默认值:部分参数会提供默认值,这部分参数都是可选参数。
² 典型值:配置文件中的典型配置参数。
大部分参数都可以不需要配置,其中参数A05、A07、A08、A13、A14、A19必须在配置文件中编写。
A20~A35的参数是warden的配置,关于warden内容可以参考【warden】。
A21~A28这组参数需要注意,如果在配置文件中将use_warden设置为true,则必须配置文件中加入warden元素,而A21~A28这组元素则可能被warden的中对应的子元素覆盖,如代码3.7所示。
代码3.7:vcap-services-base / lib /base /node_bin.rb #initialize
use_warden = parse_property(config,"use_warden",Boolean,:optional=> true, :default=> false)
if use_warden
warden_config = parse_property(config,"warden",Hash,:optional=> true)
options[:service_log_dir]= parse_property(warden_config, "service_log_dir",String)
……
end
warden子元素的配置如表3-5所示:
表3-5 warden参数配置表(不完全)
编号
参数名称
options
变量名
类型
可选
默认值
典型值
B01
use_warden
use_warden
Boolen
是
FALSE
B02
warden
warden_config
Hash
是
B03
service_log_dir
options[:service_log_dir]
String
是
B04
service_common_dir
options[:service_common_dir]
String
是
"/var/vcap/store/common"
B05
service_bin_dir
options[:service_bin_dir]
Hash
是
B06
image_dir
options[:image_dir]
String
否
B07
port_range
options[:port_range]
Range
否
B08
filesystem_quota
options[:filesystem_quota]
Boolen
是
FALSE
B09
service_start_timeout
options[:service_start_timeout]
Interger
是
3
B10
service_status_timeout
options[:service_status_timeout]
Interger
是
3
注意:在启用warden的情况下B06、B07两个参数此时必须配置,所以A24、A25两个参数此时配置会被B06、B07给覆盖。
表3-6说明参数的作用。
表3-6 参数说明(不完全)
编号
参数名称
说明
A01
index
A02
plan
A03
capacity
表示该节点可以创建多少个Node的服务实例,参考【capacity】
A04
ip_route
A05
node_id
表示当前主机的Node的名称,作为向NATS订阅主题时候使用的参数之一,参考【主题】
A06
z_interval
定时任务的中间间隔,如果不设置,则默认使用时间为30秒
A07
mbus
NATS总线,需要指明其IP地址和端口号,默认NATS使用4222
A08
local_db
创建的Service实例需要存储,默认情况下使用sqlite3
A09
migration_nfs
A10
max_nats_payload
A11
fqdn_hosts
A12
op_time_limit
在timing_exec中使用,表示一个操作的超时时间。相关内容参考【timing_exec】
A13
supported_version
该Service Node支持创建的实例的版本,一般情况下就1个版本,该参数必须在重写NodeBin时候设置,否则无法使用。
A14
default_version
使用的默认版本号。
A15
max_clients
A16
database_lock_file
A17
disabled_file
A18
logger
由node_id生成,每个Service Node会根据其Service实例生成一个log文件。所以这个参数在配置文件中是不能配置的
logging
A19
id
A20
base_dir
A21
service_log_dir
A22
service_common_dir
A23
service_bin_dir
A24
image_dir
A25
port_range
A26
filesystem_quota
A27
service_start_timeout
A28
service_status_timeout
A29
max_memory
A30
memory_overhead
A31
max_disk
A32
disk_overhead
A33
m_interval
A34
m_actions
A35
m_failed_times
B01
use_warden
B02
warden
capacity
在Service Service Node的【配置参数】一节,我们见过一个参数叫做capacity,它表示了一个Service Node可以配置的容量,也就是可以创建的Service实例个数。引入capacity的原因如下【--?猜的--】:
² 为了实现Service实例的负载均衡,一个Gateway会对应多个ServiceNode,不能在同一个节点上创建太多实例,所以每个Service Node都会将capacity提供给Gateway,Gateway以此为计算参数进行计算,选举最适合的Service Node创建实例。
² 每个ServiceNode能够创建的Service实例不可能是无限大,引入capacity可以限制一个Node创建的Service实例个数。
初始化
capacity参数可选,默认值为200,如代码3.8所示。
代码3.8:vcap-services-base / lib /base /node_bin.rb #start
:capacity =>parse_property(config, "capacity", Integer, :optional=>true,:default=>200),
如代码3.9所示,读取的capacity参数的值会传入到@capacity和@max_capacity中。其中:
² @capacity:表示剩余容量
² @max_capacity:表示最大容量
代码3.9:vcap-services-base / lib /base /node.rb #initialize
@capacity= options[:capacity]
@max_capacity= @capacity
每次Service Node重启后会恢复之前创建的Service实例,对于已经创建的Service实例已经消耗了部分capacity,所以@capacity的值初始化还需要去除已经创建的Service实例消耗的容量值。而计算这个剩余容量的过程则需要Service开发人员自行编写。
对于计算剩余容量的时机可以选择在执行pre_send_announcement方法中进行计算。例如在EchoService中,如代码3.10所示,遍历所有创建的Service实例,然后依次去除每个实例消耗的容量,最后得到@capacity。其中:
² 【ProvisionedService】是用于保存Service实例的数据库接入对象(DAO);
² @ capacity_lock就是一个锁,定义就是@capacity_lock = Mutex.new,因为在计算剩余capacity的过程中可能还会出现销毁Service实例这样的同步问题,所以需要加锁保护;
² capacity_unit方法返回每个Service实例所消耗的capacity值。
代码3.10:vcap-services / echo /lib /echo_service /echo_node.rb #pre_send_announcement
@capacity_lock.synchronizedo
ProvisionedService.all.eachdo |instance|
@capacity-= capacity_unit
end
end
注意:计算剩余capacity必须实现,最好在pre_send_announcement方法中计算,在计算剩余capacity的过程中必须对capacity进行加锁保护。
通告
@capacity的值需要告知给Gateway,以便Gateway可以知道每个ServiceNode的剩余容量,并根据该值来实现负载均衡。
通告在【周期任务】中完成,使用send_node_announcement方法,不过对@capacity的操作不是在send_node_announcement,而是在announcement方法中,这个步骤还是需要Service开发人员完成,参考【announcement】。
如代码3.12所示,在发布给Gateway的announcement信息会带上capacity相关的信息,其中:
² :available_capacity:就是剩余容量——@capacity;
² :capacity_unit:创建一个Service实例需要消耗的代价。capacity_unit方法源代码如代码3.11所示,返回值默认设置为1。Service开发人员可以通过重写该方法改变返回值。
代码3.11:vcap-services-base / lib /base /node.rb
def capacity_unit
# subclasses could overwrite this method to re-define
# the capacity unit decreased/increased by provision/unprovision
1
end
² @capacity_lock.synchronize是一个锁操作。
代码3.12:vcap-services / echo /lib /echo_service /echo_node.rb #announcement
def announcement
@capacity_lock.synchronizedo
{ :available_capacity=> @capacity,
:capacity_unit=> capacity_unit }
end
end
实例消耗
如代码3.13所示,Service Node每创建一个新Service实例,就会减少capacity_unit份额,更多详细内容可以参考【on_provision】。
代码3.13:vcap-services-base / lib /base /node.rb #on_provision
def on_provision(msg, reply)
……
@capacity_lock.synchronize{@capacity-= capacity_unit }
……
同理,Service Node每销毁一个Service实例,就会增加capacity_unit的容量,如代码3.14所示,更多内容可以参考【on_unprovison】。
代码3.14:vcap-services-base / lib /base /node.rb #on_unprovision
def on_unprovision(msg, reply)
……
@capacity_lock.synchronize{@capacity+= capacity_unit }
……
warden
【--?--没怎么看懂它做什么用】
NATS与Event Machine
NATS作为整个CloudFoundry的消息总线,不作为本文介绍的重点。不过在下文运行过程介绍中会经常遇到它。所以还是需要知道一下它的一些简单使用。可以参考博文:http://blog.csdn.net/zdq0394/article/details/7860041
主题
在连接到NATS后,每个ServiceNode都会向节点订阅它所关心的主题,相关内容参考【订阅主题】。
订阅主题的过程在Base::Node的on_connect_node方法中实现,实现过程如代码5.1所示。
代码5.1:vcap-services-base / lib /base /node.rb #on_connect_node
%w[provision unprovision bind unbind restore disable_instance enable_instance import_instance update_instance cleanupnfs_instance purge_orphan].eachdo |op|
eval%[@node_nats.subscribe("#{service_name}.#{op}.#{@node_id}") { |msg, reply| EM.defer{ on_#{op}(msg, reply) } }]
end
该段代码在句法上有些复杂,我们来慢慢剖析:
² %w[provision unprovision ……].each do |op| …… end :这个句法的意思就是一次遍历方括号中的每个元素,元素的类型为String,取出的元素至于op中,然后执行代码块中的内容。
² eval %[……] :这个句法的意思就是,把内部的文本内容翻译成Ruby可执行的语句,使其可以执行。
² @nodes_nats.subscribe(……) { |msg, reply| ……} :这个句法的意思是,向NATS订阅主题,每个主题对应的处理方法则是在其代码块中定义。
² "#{service_name}.#{op}.#{@node_id}":这个句法代表订阅的主题。一个主题由三者共同确定(注:部分主题只需要2个部分内容)。对于同一个Service,service_name其实是相同的;op是模板已经写好的,不能修改;@node_id则必须不同,目的是为了区分不同主机上的Service Node(注意:不是区分Service实例):
ü service_name:标识Service的名称,一般是”XXXaaS”格式,这个名字是由Service开发人员编写的,详细内容参考【service_name】。
ü op:订阅的主题操作,也就是%w[……]中的内容,Node中已经提供了大量的操作,Service开发人员在编写Node时最多的时间也就是对这些操作的开发。
ü @node_id:标识一个Service Node,这样Gateway才能准确将信息发送给指定Node。
注意:@node_id是在配置文件中进行配置的node_id参数,参考【配置参数】
² EM.defer{on_#{op}(msg, reply)} :这个部分的内容则是实现了相应主题的处理函数(在Event Machine中运行该处理方法)。对应主题的处理方法名称就是”on_XXX”。这些处理方法提供了处理主题的一份模板,大部分主题会预留Hook方法让Service开发人员完善,实现所需的Service Node开发。
表5-1整理了Node中的主题内容:
表5-1 Service Node订阅主题相关说明(不完全)
编号
主题名称
处理方法
Hook方法
说明
F01
provision
on_provision
provision
创建一个Service实例
F02
unprovision
on_unprovision
unprovision
销毁一个Service实例
F03
bind
on_bind
bind
将Service实例与APP绑定
F04
unbind
on_unbind
unbind
将Service实例与APP解除绑定
F05
restore
on_restore
F06
disable_instance
on_disable_instance
disable_instance
dump_instance
让Service实例停止服务,但不销毁
F07
enable_instance
on_enable_instance
enable_instance
让Service实例生效
F08
import_instance
on_import_instance
import_instance
F09
update_instance
on_update_instance
update_instance
F10
cleanupnfs_instance
on_cleanupnfs_instance
F11
purge_orphan
on_purge_orphan
F12
check_orphan
on_check_orphan
F13
discover
on_discover
² 主题名称:对应处理的主题。其中主题F01~F11主题内容是"#{service_name}.#{op}.#{@node_id}"格式;F12、F13主题内容是"#{service_name}.#{op}" 格式。另外,{F01~F09-F05}明确要求Service开发人员实现相关的处理方法。【--?--F05有些奇怪,我无法把它串联起来】
² 处理方法:就是相应主题的处理方法。
² Hook方法:处理方法中帮助Service开发人员完成了一些基本工作,真正的处理都需要在Hook方法中进行处理。
对于如何添加新的主题,Service的开发人员其实不用关心,因为Cloud Foundry已经为我们设计好了模板,而且这份模板已经足够使用,而且添加新的主题是一件非常麻烦的事情。我们所关心的事情是,对于相应主题的处理方法的调用过程以及Hook方法的实现,这些处理方法将在【运行过程】中一一展开讲述。
周期任务
在Service Node中,一共需要执行2个周期任务。
周期任务底层依靠Event Machine实现,EventMachine中添加周期任务不难,只需要调用add_periodic_timer方法,语法格式如代码6.1所示.
代码6.1
EM.add_periodic_timer(10) do
# do something
end
send_node_announcement
第一个周期任务在Base::Node#on_connect_node添加,如代码6.2所示。其中:
² pre_send_announcement方法可以参考【pre_send_announcement】,这个方法默认情况下为空,也就是指它是一个Hook函数,Service的开发人员可以通过重写它实现在进行周期任务前的一些预处理。
² EM.add_periodic_timer(30) {……}:是在EventMachine中添加一个30S的定时器,定期执行代码段中的内容。
² 周期任务的主要流程都在send_node_announcement方法中。该方法就是用于【--?--向Gateway进行注册以及保活】,让Gateway可以实时监测到Node的状态信息。
代码6.2:vcap-services-base / lib /base /node.rb #on_connect_node
def on_connect_node
……
pre_send_announcement
send_node_announcement
EM.add_periodic_timer(30) { send_node_announcement }
end
如代码6.3所示,send_node_announcement方法做的事情就是整理需要发送的announcement信息到发布主题#{service_name}.announce,这样就可以发送到对应的Gateway处理。
² 在发送announcement之前,需要确定节点是否是正常运行的,只有在正常运行的情况下才能发送announcement。
² 在发送的announcement信息中,有三个信息是必须存在,也就是@node_id、@plan、@supported_version。这三个参数也是在配置文件中必须指定的,参考【配置参数】。
² 还可以添加自定义的announcement信息,Service的开发人员通过announcement方法添加,参考【announcement】。
不过这个方法实现有些诡异,在函数定义的时候我们明显看到它带有2个参数,但是在代码6.2中我们可以看到并没有代入参数,还有就是msg为nil时候就可以发送announcement。
代码6.3:vcap-services-base / lib /base /node.rb #send_node_announcement
def send_node_announcement(msg=nil, reply=nil)
if disabled? ……return end
unless node_ready? ……return end
req = nil
req = Yajl::Parser.parse(msg)if msg
if !req || req["plan"]== @plan
a = announcement
a[:id]= @node_id
a[:plan]= @plan
a[:supported_versions]= @supported_versions
publish(reply|| "#{service_name}.announce",Yajl::Encoder.encode(a))
end
……
end
announcement
Service Node定期调用【send_node_announcement】方法向Gateway发送Node的声明,声明的内容除去三个必须含有的信息——@node_id、@plan、@supported_version——外,官方专门预留announcement方法提供给service开发人员进行扩展。
如代码6.4所示,官方要求必须实现这个方法。announcement方法需要处理的内容就是需要提供给Gateway的信息。
代码6.4:vcap-services-base / lib /base /node.rb
# Service Node subclassesmust implement the following methods
# announcement() --> {any service-specific announcement details }
abstract :announcement
表6.1整理了announcement方法的参数说明,
表6.1announcement方法说明
函数名:announcement
参数名称
说明
输入参数
-
返回值
Hash
返回需要通告信息的Hash表,最后该表会发送给Gateway
如代码6.5所示,在EchoService中,announcement方法返回值就是一个Hash数组。
代码6.5:vcap-services / echo /lib /echo_service /echo_node.rb
def announcement
@capacity_lock.synchronizedo
{ :available_capacity=> @capacity,
:capacity_unit=> capacity_unit }
end
end
Gateway on_announce
Service Node定期发送的announcement最后由ServiceGateway接收。在代码6.3中,Service Node每次发送announcement周期任务的时候使用的主题是“#{service_name}.announce”。如代码6.5.1所示,在ServiceGateway中,对应于该主题的处理方法是on_announce方法。
代码6.5.1:vcap-services-base / lib / base / provisioner.rb #on_connect_node
def on_connect_node
@logger.debug("[#{service_description}] Connected to node mbus..")
%w[announce node_handles handles update_service_handle].eachdo |op|
eval%[@node_nats.subscribe("#{service_name}.#{op}") { |msg, reply| on_#{op}(msg, reply) }]
end
如代码6.5.2所示,【--?--】
代码6.5.2:vcap-services-base / lib / base / provisioner.rb #on_announce
def on_announce(msg, reply=nil)
announce_message =Yajl::Parser.parse(msg)
if announce_message["id"]
id= announce_message["id"]
announce_message["time"]= Time.now.to_i
if @provision_refs[id]> 0
announce_message['available_capacity']= @nodes[id]['available_capacity']
end
@nodes[id]= announce_message
end
end
update_varz
第二个周期任务任务在Base::Node#initialize方法中添加,如代码6.6所示。其中
² z_interval就是【配置参数】中配置,如果不配置,则会使用默认值30。
² 周期任务的主要流程都在update_varz中执行。
代码6.6:vcap-services-base / lib /base /node.rb #initialize
z_interval = options[:z_interval]|| 30
EM.add_periodic_timer(z_interval)do
EM.defer {update_varz }
end if @node_nats
……
end
update_varz定义如代码6.7所示
² 首先通过varz_details方法获取到需要想Component注册的信息表(Hash),关于varz_details的内容参考【varz_details】
² 然后向Component中依次注册每个参数。
代码6.7:vcap-services-base / lib /base /base.rb
def update_varz()
vz = varz_details
vz.each { |k,v|
VCAP::Component.varz[k] = v
} if vz
end
varz是CloudFoundry用于监控组件状态,大致原理如下【--?--我还是不知道做什么的?】:
Cloud Foundry状态监控组件:VARZ原理
在代码中我们可以看到组件在启动时都会向Component注册自己。那么这个注册就会启动一个http server。启动的代码在vcap common的component.rb中。这个模块已经成为了一个gem,你不必装Cloud Foundry,而只需要gem install一个都可以使用了。在vcap common中,如果有组建来注册,他会为这个组件建立一个server。然后server的port以及帐号密码默认是cf自己生成的。但是按照上文的配置,这些参数就会被传入,我们就可以按照自己的参数来配置这个server了。
在组件向component注册完成之后,组建就可以通过以下方式向varz传数据了:
[ruby]view plaincopy
1 #这是dea的状态更新
2 VCAP::Component.varz[:running_apps] = running_apps
3 VCAP::Component.varz[:frameworks] = metrics[:framework]
4 VCAP::Component.varz[:runtimes] = metrics[:runtime]
varz_details
在【update_varz】中介绍了CloudFoundry中每个组件都会向VCAP::Component注册自己,然后可以向varz传入参数。的传入的参数则是根据varz_details得到。代码6.8给出了varz_details的定义,默认情况下varz_details的返回值就是announcement的返回值。该方法可以重写,只需要返回内容是Hash表即可。
代码6.8:vcap-services-base / lib /base /node.rb
def varz_details
# Service Node subclassesmay want to override this method to
# provide service specific data beyond what is returned by their
# "announcement" method.
return announcement
end
注意:varz_details方法可以重写。如代码6.9所示,在MySqlService实现中就重写了该方法。
代码6.9:vcap-services / mysql /lib /mysql_service /node.rb
def varz_details()
acquired = @varz_lock.try_lock
return unless acquired
varz = {}
# how many queries served since startup
varz[:queries_since_startup]= get_queries_status
# queries per second
varz[:queries_per_second]= get_qps
# disk usage per instance
status = get_instance_status
varz[:database_status]= status
……
# how many long queries and long txs are killed.
varz[:long_queries_killed]= @long_queries_killed
……
# how many provision/binding operations since startup.
@statistics_lock.synchronizedo
varz[:provision_served]= @provision_served
varz[:binding_served]= @binding_served
end
# provisioned services status
varz[:instances]= {}
begin
ProvisionedService.all.eachdo |instance|
varz[:instances][instance.name.to_sym] = get_status(instance)
end
……
varz[:connection_pool]= @pool.inspect
varz
……
end
小结
Service Node的周期任务并不复杂。
相关的配置参数只有一个z_interval。该参数配置了每次执行update_varz的间隔时间。
需要实现的方法也只有一个:announcement,该方法规定了发布给Gateway的信息内容。该方法要求返回一个Hash表。
varz_details方法默认情况下使用announcement的返回值,Service开发人员可以根据自己的需求重新实现该方法。
运行过程
Service Node在初始化完成后,在运行过程中的工作任务除了【周期任务】中的两个外,最主要职责就是负责Service实例的创建与维护。而Service Node如何知道自己在什么时刻应该执行什么功能?其实也简单,通过之前订阅的主题进行驱动(参考【订阅主题】、【主题】),当Service Node接收到其订阅的主题的请求后,就会调用相应的方法,而调用的方法的格式也就是【主题】一节中说的”on_xxx”格式。
Base::Node为Service的开发人员实现了这份模板,并且为其中的关键部分都留出了抽象方法让Service开发人员可以自定义在交互过程中提供的变化。相应的说明可以参考【主题】一节中的表格。
对于主题的处理过程其实是一样的,我们会在【创建Service instance】一节中细致的分析该过程,在之后的章节中略去这些重复说明,如果有不懂的地方可以类比【创建Service instance】中的过程。
创建与销毁
创建Service instance
provision主题
创建Service实例使用的主题内容是provision。这点很容易理解,不过需要注意的一点就是,创建的Service实例此时还未与APP进行绑定,绑定动作是在bind主题中完成的。此外,为了提高运行效率,更推荐在创建Service实例时采用Lazy技术,也就是延迟向Service服务器程序申请创建Service实例的时机,直到要求将APP与Service实例进行绑定的时候才选择想Service服务器程序创建相应的实例。这点将会在下面的分析中体现出来。
在【主题】一章中我们知道Node在启动的时候会订阅主题“#{service_name}.provision.#{@node_id}”。当用户通知CloudController创建一个Service实例的时候,Cloud Controller让Gateway选出一个Service Node创建Service实例,此时Gateway就会发布一个这样的主题内容,NATS会将其正确的发送给对应的Service Node,Service Node就根据收到的主题内容使用对应的处理函数进行处理。
provision过程
一个Service实例的创建过程大致如图7-1所示:
1) 用户使用命令vmc create 请求创建一个Service实例,这个请求被CloudController捕获。
2) CloudController根据vmc命令请求要求对应的Gateway处理该任务。
3) 这时候Gateway会调用provision_service方法从它管理的Service Node中选择一个最优的Service Node(best_node),整理好所有必需信息以后发布#{service_name}.provision.#{@node_id}这样的一份主题,NATS会负责把它发送给Base::Node。
4) 然后Base::Node中的on_provision方法接收到了这份主题请求,参考【on_provision】,在进行部分处理以后,
5) 调用provision方法创建Service实例——这个方法要求Service的开发人员进行编写,返回值要求是一个Hash数组,相关内容参见【provision】
6) 如果provision方法正确创建了一个Echo的实例,此时Base::Node会将相关的信息(哪些信息由Service的开发人员决定)通过encode_success方法编码后返回给Gateway,这样就创建了一个Service实例。
图 7-1 Service实例创建时序图(示意)
on_provision
on_provision的源代码如代码7.1所示,其中:
² roolback是一个代码块,他的作用是当创建实例失败的时候调用unprovision方法将进行到一半的实例进行析构。
² timing_exec提供的功能就是在@op_time_limit时间内如果还无法成功创建Service实例,则调用roolback代码块,关于timing_exec方法参见【timing_exec】。
² 在timing_exec中间就会调用provision方法创建一个Service实例,关于provision方法参考【provision】。在这里会扣减capacity容量,相关内容可以参考【capacity】。如果创建成功,则讲成功的结果整理编码后反馈给Gateway。
代码7.1:vcap-services-base / lib /base /node.rb #on_provision
def on_provision(msg, reply)
response = ProvisionResponse.new
rollback= lambdado |res|
@capacity_lock.synchronize{@capacity += capacity_unit } if unprovision(res.credentials["name"],[])
end
timing_exec(@op_time_limit, rollback)do
provision_req = ProvisionRequest.decode(msg)
plan = provision_req.plan
credentials = provision_req.credentials
version = provision_req.version
credential = provision(plan, credentials, version)
credential['node_id']= @node_id
response.credentials = credential
@capacity_lock.synchronize{@capacity-= capacity_unit }
response
end
publish(reply, encode_success(response))
……
end
provision
provision方法要求Service开发人员必须实现,其定义如代码7.2所示,它需要实现的功能就是实现在Local Node中创建一个Service 实例。
代码7.2:vcap-services-base / lib /base /node.rb
# Service Node subclassesmust implement the following methods
# provision(plan) --> {name, host, port, user, password}, {version}
abstract :provision
注意:注释中给出了相应的参数与返回值,但是在使用的时候却又出现了差异,这应该是Cloud Foundry的开发人员忘记修改注释引起。在使用provision的时候实际传入的是三个参数(注意:Ruby中没有函数重载),返回类型也存在差异,所以注释中给定的信息并不可信。
表7-1整理了该方法的参数说明。
表7-1provision方法说明
函数名:provision
参数名称
说明
输入参数
plan
Gateway发送过来的plan值。
credentials
为一个Hash数组,其中包含了用户创建的Service实例的名字。
version
使用的Service版本号。
返回值
credentials
credentials是一个Hash表,推荐含有name,host,port,user,password几个键值。
如代码7.3所示,其显示了provision带入参数的来源以及provision方法的调用过程。provision使用的参数从通过ServiceGateway发送的请求信息中提取。需要注意credentials参数的来源。
注意:除credentials外还有一个参数是credential,少个s。
代码7.3:vcap-services-base / lib /base /node.rb
def on_provision(msg, reply)
provision_req= ProvisionRequest.decode(msg)
plan = provision_req.plan
credentials= provision_req.credentials
version = provision_req.version
credential = provision(plan, credentials, version)
……
从代码7.4给出了Service Gateway整理发送给ServiceNode的这几个参数值的来源。
代码7.4:vcap-services-base / lib /base /provisioner.rb #provision_service
def provision_service(request,prov_handle=nil, &blk)
……
prov_req = ProvisionRequest.new
prov_req.plan = plan
prov_req.version = version
# use old credentials to provision a service if provided.
prov_req.credentials= prov_handle["credentials"]if prov_handle
实现provision
我们考察Echo Service中provision方法的实现,如代码7.5,其中credentials这个参数中包含的内容就是使用vmc create进行创建一个service的时候传入的参数【--?这个地方有待验证--】。所以credentials参数中最少会含有一个参数名称“name”。
注:这个name参数是用户希望创建的Service实例的名字。
代码7.5:vcap-services / echo /lib /echo_service /echo_node.rb #provision
def provision(plan, credential= nil, version=nil)
instance = ProvisionedService.new
if credential
instance.name =credential["name"]
……
也可以包含“user”与“password”,例如在代码7.6中,MySql::Node的provision方法就就可以看到如下三个参数:
代码7.6:vcap-services / mysql /lib /mysql_service /node.rb #provision
def provision(plan, credential=nil, version=nil)
……
if credential
name, user, password= %w(name user password).map{|key| credential[key]}
……
如何创建一个Service实例,这个需要由Service开发人员实现。如代码7.7所示,我们考察Echo::Node中的provision方法,其中:
² ProvisionedService就是一个DAO(Database Access Object)。内部封装了一个Ruby数据库ORM——DataMaper。详细内容参考【ProvisionService】
² Echo中创建实例的过程相对简单,首先在数据库中创建相应Service实例,然后保存实例。
² 最后将用户访问Service实例所需要的信息(如主机号、端口号、用户名、登陆密码等)返回。
代码7.7:vcap-services / echo /lib /echo_service /echo_node.rb #provision
def provision(plan, credential= nil, version=nil)
#class ProvisionedService
# include DataMapper::Resource
# property :name, String, :key => true
#end
instance = ProvisionedService.new
if credential
instance.name = credential["name"]
……
begin
save_instance(instance)
……
# gen_credential(instance)
credential = {
"host"=> get_host,
"port"=> @port,
"name"=> instance.name
}
end
图7-2整理了provision方法实现的时候的一个执行流程。如果没有特殊的要求,一般都可以按照这个步骤进行编写。Cloud Foundry中对于Service实例的序列化默认提供sqlite进行管理,参考【ProvisionService】。
图7-2provision执行流程
销毁Service instance
销毁一个Service实例,过程与provision类似,对应主题“#{service_name}.unprovision.#{@node_id}”。在收到unprovision主题后由on_unprovision方法处理。
on_unprovision
on_unprovision是on_provision方法的一个逆过程,它的做法就是销毁一个已经存在的Service实例。其源码如代码7.8所示。
² 因为创建一个Service实例的主要动作是Service开发人员在provision方法中编写的,CloudFoundry无法得知如何去注销个Service实例,所以也提供了一个相应的方法——unprovision方法,该方法也需要Service开发人员进行编写。
² 方法中提供了unprovision使用的两个参数,name是需要销毁的service实例的名字;bindings是与该service实例绑定的APP信息。
代码7.8:vcap-services-base / lib /base /node.rb #on_provision
def on_unprovision(msg, reply)
……
unprovision_req = UnprovisionRequest.decode(msg)
name= unprovision_req.name
bindings = unprovision_req.bindings
result = unprovision(name, bindings)
if result
publish(reply, encode_success(response))
……
unprovision
unprovision定义如代码7.9所示,unprovision方法必须重写,它实现了销毁名字为name的Service实例。
代码7.9:vcap-services-base / lib /base /node.rb
# Service Node subclasses must implement the following methods
# unprovision(name) --> void
abstract :unprovision
源码中对于unprovision的注释存在一些问题,其需要带入两个参数,必须有1个返回值。整理后入表7-2所示:
表7-2unprovision方法说明
函数名:unprovision
参数名称
说明
输入参数
name
Service实例名字
bindings
与该Service实例绑定的APP信息
返回值
bool
是否成功
实现unprovision
相应的,我们考察Echo::Node中unprovision方法的实现,如代码7.10所示。这个代码会出现一些不协调的地方,
² 首先,它将参数bindings的名字改成了credentials,这样的命名容易让人误解其含义;
² 然后,这个方法中并未使用到credentials这个参数,之后我们会考察MySql中是如何使用这个参数的。
² 最后会根据结果返回一个布尔值表明是否成功注销Service实例。
代码7.10:vcap-services / echo /lib /echo_service /echo_node.rb
def unprovision(name,credentials = [])
return if name.nil?
instance = get_instance(name)
raiseEchoError.new(EchoError::ECHO_DESTORY_INSTANCE_FAILED,instance.inspect)unless instance.destroy
true
end
为了更好的理解unprovision方法,我们考察了MySql下该方法中对于credentials参数的使用,如代码7.11所示。如果看过【on_bind】,就知道在对一个Service实例进行注销的时候,必须对bind操作进行一个逆向操作,也就是unbind。所以在MySql::Node中就会调用unbind方法将Service实例和与之绑定的APP解除绑定。
代码7.11:vcap-services / mysql /lib /mysql_service /node.rb #unprovision
# TODO: validate that database files are not lingering
# Delete all bindings, ignore not_found error since we are unprovision
begin
credentials.each{ |credential|unbind(credential)} if credentials
注意:unprovision方法的执行流程与provision方法是一样的,可以参考图-2。不仅如此,bind、unbind等方法的执行流程也是一样的,下文中就列举了。
绑定与解除绑定
APP与Service instance绑定
创建的Service实例对于APP而言是不可见的,甚至Service还不知道需要创建Service实例(如:在MySql的provision方法中使用Lazy技术延迟创建数据库),用户部署的APP如果希望能够见到Service实例,就需要将APP与Service实例进行绑定。APP与Service绑定过程类似于Service实例的创建过程,对应的主题是“#{service_name}.bind.#{@node_id}”。在收到unprovision主题后由on_bind方法处理。
on_bind
如代码7.12所示,on_bind的源码格式和on_provision的格式基本相同,也是确定参数,调用bind方法,向Service Gateway返回结果这样一个流程。
代码7.12:vcap-services / mysql /lib /mysql_service /node.rb
def on_bind(msg, reply)
response = BindResponse.new
rollback = lambdado |res|
unbind(res.credentials)
end
timing_exec(@op_time_limit, rollback)do
bind_message = BindRequest.decode(msg)
name= bind_message.name
bind_opts = bind_message.bind_opts
credentials = bind_message.credentials
response.credentials= bind(name, bind_opts, credentials)
response
end
publish(reply, encode_success(response))
……
end
bind
bind方法也是预留给Service开发人员使用的接口,这个方法在源码中的定义如代码7.13所示。注释给出的参数也存在一些偏差,表7-3整理了bind方法的用法。
代码7.13:vcap-services-base / lib /base /node.rb
# Service Node subclasses must implement the following methods
# bind(name, bind_opts) --> {host, port, login, secret}
abstract :bind
表7-3 bind方法说明(不完整)
函数名:bind
参数名称
说明
输入参数
name
APP需要绑定的Service实例的名字
bind_opts
绑定时候附加的参数
credentials
返回值
credentials
实现bind
Echo::Node中的bind方法并没有太多的参考性,我们可以参考MySql::Node中的bind方法的实现,如代码7.14所示:
² 首先、从数据库中找到存储的Service实例(参考【provision】中介绍的Echo::Node的provision方法,用的数据库是一样的);
² 然后也是创建一个数据库存储绑定的APP的信息;
² 再之后调用enforce_instance_storage_quota方法在MySql中创建这个账户(注:不是创建了Service实例就会在MySql中创建这个账户,而是在绑定的时候才会创建,所以才会产生enforce_instance_storage_quota这个方法调用);
² 最后会将结果信息返回给Gateway。
代码7.14:vcap-services / mysql /lib /mysql_service /node.rb
def bind(name, bind_opts, credential=nil)
begin
service = ProvisionedService.get(name)
# create new credential for binding
binding= Hash.new
if credential
binding[:user]= credential["user"]
binding[:password]= credential["password"]
……
binding[:bind_opts]= bind_opts
begin
create_database_user(name,binding[:user],binding[:password])
enforce_instance_storage_quota(service)
……
response = gen_credential(name,binding[:user],binding[:password])
……
end
APP与Service instance解除绑定
将APP与Serviceinstance解除绑定,对应主题“#{service_name}.unbind.#{@node_id}”。在收到unbind主题后由on_unbind方法处理。
on_unbind
on_unbind是on_bind的逆过程,处理内容就是将APP与Service实例解除绑定。如代码7.15所示,真正执行解除绑定的任务是在unbind方法中调用。
代码7.15:vcap-services-base / lib /base /node.rb
def on_unbind(msg, reply)
response = SimpleResponse.new
unbind_req = UnbindRequest.decode(msg)
result = unbind(unbind_req.credentials)
if result
publish(reply, encode_success(response))
……
unbind
unbind方法定义如代码7.16所示,表7-4整理了unbind方法说明。
代码7.16:vcap-services-base / lib /base /node.rb
# unbind(credentials) --> void
abstract :unbind
表7-4unbind方法说明(不完全)
函数名:unbind
参数名称
说明
输入参数
ccredentials
返回值
bool
是否成功
实现unbind
on_restore
【--?--】
管理Service instance方法
Base::Node中提供了一组方法用于管理Service实例的主题与接口。包括了Service实例的启动、停止、导入、更新等常用操作。这些方法定义如代码7.17所示:表7-5整理了相应的主题与方法。
代码7.17:vcap-services-base / lib /base /node.rb
# <action>_instance(prov_credential, binding_credentials) --> true for success and nil for fail
abstract :disable_instance,:dump_instance,:import_instance,:enable_instance,:update_instance
表7-5 Service实例管理方法
主题
处理方法
Hook方法
说明
disable_instance
on_disable_instance
disable_instance
dump_instance
enable_instance
on_enable_instance
enable_instance
import_instance
on_import_instance
import_instance
update_instance
on_update_instance
update_instance
cleanupnfs_instance
on_cleanup_instance
孤儿实例的管理
orphan
何谓orphan,其实orphan就是在service节点上已经创建的一个service实例,但是它的存在还没有通知cloud_controller。比如说,在service节点创建完实例并通知完service gateway,而当gateway返回给cloud_controller时,发生了网络故障,从而cloud_controller通知终端用户,创建失败,当然也不会有信息更新在cloud_controller的数据库中。这样的话,就相当于在service_node上创建了一个没有用的实例,从而导致浪费了一些资源。orphan和没有绑定的instance是有区别的,在实际情况中经常会出现未经绑定的instance,但是他们在cloud_controller中都是有数据记录的,而orphan则没有。一般这种情况很罕见,但是源码中还是考虑了这一点。
on_check_orphan
on_purge_orphan
orphan的几个管理函数
小结
杂项
在分析源码的过程中还会遇到许多的辅助方法,这些方法因为与章节内容相关性很小,一直找不到地方放置。所以干脆就直接兜在一块说,这个章节的内容很零散,不需要专门阅读,只有在用到了或者看不懂的时候再看。
abstract
在前面的章节中经常可以看到“abstract:XXX”这样的语法格式。因为我没有专门学过Ruby,尤其对于Ruby元编程的内容更加不了解。以下内容都是个人的臆测。
首先,我们先找到abstract的定义(一开始我以为它是一个关键字,后来发现不是),如代码8.1所示,它其实也是一个方法,有自己的参数。
该方法就是调用了define_method方法检查是否实现了args中定义的方法。它所想要实现的就是类似于C++中的纯虚函数定义。所以使用了abstract :XXX表示的语句我们可以看做是定义了一个纯虚函数XXX,我叫它——抽象方法。
代码8.1:vcap-services-base / lib /base /abstract.rb
classClass
def abstract(*args)
args.each do |method_name|
define_method(method_name)do |*args|
raise NotImplementedError.new("Unimplemented abstract method #{self.class.name}##{method_name}")
end
end
end
end
node_class
在【创建Echo::Node实例】中遇到了node_class,该方法其实很容易看明白。特殊地方就在于node_class方法要求Echo::NodeBin中进行实现,重写内容很简单,只需要指向相应的XXX::Node即可。
表8-1node_class方法说明
函数名:node_class
参数名称
说明
输入参数
-
返回值
Node
Service的Node类,例如VCAP::Service::Echo::Node
例如在Echo Service实现的时候,参照代码8.2,它就返回了Echo中实现的Service Node。
代码8.2:vcap-services / echo /bin /echo_node
classVCAP::Services::Echo::NodeBin<VCAP::Services::Base::NodeBin
……
def node_class
VCAP::Services::Echo::Node
end
……
end
注意:该方法要求Service开发者必须实现。
pre_send_announcement
在Base::Node中pre_send_announcement方法的定义为空,也就是说它是一个Hook方法,可以让Service的开发人员自定义实现该方法,当然也可以不实现。这个方法所在的上下文有:
² 已经完成了到NATS的连接建立过程,并且完成了主题的订阅。
² 在添加周期任务send_node_announcement之前执行
对于Node的初始化有两个时机,一个是Node的initialize方法中,另一个就是在pre_send_announcement方法。前者多用于参数的初始化,而在pre_send_announcement方法中更适合Node数据库的初始化(就是使用【配置参数】中local_db)以及剩余capacity的计算。
如代码8.3所示,在Echo Service实现中,pre_send_announcement的工作就是初始化了使用的数据库,以及对剩余capacity的计算。
代码8.3:vcap-services / echo /lib /echo_service /echo_node.rb
def pre_send_announcement
super
FileUtils.mkdir_p(@base_dir)if @base_dir
start_db
@capacity_lock.synchronizedo
ProvisionedService.all.eachdo |instance|
@capacity-= capacity_unit
end
end
end
@logger
Cloud Foundry实现了实现了一个日志文件系统,每个组件都会存在一个@logger实例,它就表示了该日志系统。我们并不关心该日志系统的实现,更关注于它的使用。
从代码8.4可以看出,与日志文件系统的相关配置参数是logging,每个节点的日志是根据node_id生成的。Logging参数配置可以参考【配置参数】。在开发期间还是选择debug模式。
代码8.4:vcap-services-base / lib /base /node_bin.rb #start
VCAP::Logging.setup_from_config(config["logging"])
# Use the node id for logger identity name.
options[:logger]= VCAP::Logging.logger(options[:node_id])
@logger= options[:logger]
使用@logger也很简单,我们一般使用debug和info方法(我没去看日志系统,也不知道有没有其他高级特性)。例如如代码8.5,我们截取了两个使用日志系统的例子。
代码8.5:vcap-services-base / lib /base /node.rb
@logger.info("#{service_description}: Not sending announcement because node is disabled")
@logger.debug("#{service_description}: Not ready to send announcement")
timing_exec
在【运行过程】一节中,会经常遇到timing_exec方法。这个方法其实很简单,就是要求在time_limit时间内完成代码块中的内容,如果超时,就调用roolback并抛出异常,如代码8.6所示。
代码8.6:vcap-services-base / lib /base /node.rb #timing_exec
def timing_exec(time_limit, rollback=nil)
return unless block_given?
start = Time.now
response = yield
if response&& Time.now- start > time_limit
rollback.call(response)if rollback
raise ServiceError::new(ServiceError::NODE_OPERATION_TIMEOUT)
end
end
ProvisionedService
在看Echo::Node和MySql::Node源码时候就会看到这个类。其实这个类名字是什么无所谓,关键的是知道它是做什么用的。我们都知道Service Node创建Service实例,但是这个实例不可能只存储内存中,否的一宕机Service实例的内容就没有了,所以就需要支持Service实例的可序列化。当然我们也可以使用XML这种格式存储,不过Cloud Foundry中则是使用了sqlite3进行保存,然后对数据库的中间层使用的是DataMapper。
如代码8.7所示,其中:
² ProvisionService其实就是表示了在数据库中存储的一个Service实例;
² include DataMapper::Resource 则是导入了对数据库操作的相关接口,它提供了包括创建、查询、保存等数据库常用操作;
² property XXX 就是一个数据库表,Service开发人员在这里定义希望在数据库中保存的Service信息。
代码8.7:vcap-services / echo /lib /echo_service /echo_node.rb
class ProvisionedService
includeDataMapper::Resource
property :name,String, :key => true
end
在Node启动的时候,还需要调用代码8.8中的代码。该代码的调用时机一般就选择在【pre_send_announcement】中。其中@local_db参考【配置参数】中的说明。
代码8.8::
DataMapper.setup(:default,@local_db)
DataMapper::auto_upgrade!
如表8-2所示,我们截取了一些常用的操作的示例代码。
表8-2ProvisionService常用操作
操作
示例代码
创建一个新项
instance =ProvisionedService.new
保存一个项
instance.save
删除一个项
instance.destroy
获取特定项
instance =ProvisionedService.get(name)
遍历素有项
ProvisionedService.all.eachdo |instance| …… end
service_name
每个Service需要一个名字(name),在【订阅主题】和【主题】两节中,我们知道service_name作为Node订阅主题中关键的一个数据结构。如代码8.9所示,源码中要求Service Node和Provisioner节点都必须实现该方法。
代码8.9:vcap-services-base / lib /base /base.rb
# Service Provisioner and Node classes must implement the following
# method
abstract :service_name
表8-2整理的service_name方法的定义
表8-2service_name方法说明
函数名: service_name
参数名称
说明
输入参数
-
返回值
name
需要返回一个字符串,参考代码5.1即可知道。
按理来说,该方法在Node类中实现即可。不过按照CloudFoundry中Service实现的惯例来看,一般是将这个方法封装在Common模块中。这是因为这个方法是Node类和Provisioner类都需要的,为了避免两者重复实现,所以将其封装成了接口。
如代码8.10所示,在EchoService中,专门对service_name的实现封装,然后在代码8.11的Node类实现过程中,导入该接口,在Service的Provisioner类实现中也是类似的做法。
代码8.10:vcap-services / echo /lib /echo_service /common.rb
moduleVCAP
module Services
module Echo
module Common
defservice_name
"EchoaaS"
end
end
end
end
end
代码8.11:vcap-services / echo /lib /echo_service /echo_node.rb
classVCAP::Services::Echo::Node
includeVCAP::Services::Echo::Common
注意:该方法要求Service开发人员必须实现
all_instance_list
在【on_check_orphan】一节中,我们见到on_check_orphan方法调用了all_instance_list方法,该方法的定义如代码8.11所示。
² Service Node需要监测orphan的Service实例,关于orphan内容参考【orphan】,所以每次需要将所有需要检查的Service实例列表发送给Gateway,返回该列表的工作就由all_instance_list方法完成。
² 这个方法要求实现,默认情况下会返回一个空列表,意思是,如果Service开发者不实现该方法也不会报错,但是在运行过程中产生的orphan都不会被管理。
代码8.11:vcap-services-base / lib /base /node.rb
# Subclass must overwrite this method to enablecheck orphan instance feature.
# Otherwise it will not check orphan instance
# The return value should be a list of instance name(handle["service_id"]).
def all_instances_list
[]
end
如果要实现该方法,其实也不复杂。
表8-3all_instance_list方法说明
函数名:all_instance_list
参数名称
说明
输入参数
-
返回值
list
返回一个Service实例列表
如代码8.12所示,在MySql::Node中实现该方法,它返回的是数据库中所有保存的Service实例。
代码8.12:vcap-services / mysql /lib /mysql_service /node.rb
def all_instances_list
ProvisionedService.all.map{|s| s.name}
end
注意:该方法要求Service开发人员必须实现。
all_binding_list
vcap-services-base /lib /base /node.rb
# Subclass must overwrite this method to enablecheck orphan binding feature.
# Otherwise it will not check orphan bindings
# The return value should be a list of binding credentials
# Binding credential will be the argument for unbind method
# And it should have at least username & name property for base code
# to find the orphans
def all_bindings_list
[]
end
vcap-services /mysql /lib /mysql_service /node.rb
def all_bindings_list
res = []
all_ins_users = ProvisionedService.all.map{|s| s.user}
@pool.with_connectiondo |connection|
# we can't query plaintext password from mysql since it's encrypted.
connection.query('select DISTINCT user.user,db from user, db where user.user = db.user and length(user.user) > 0').eachdo |entry|
# Filter out the instances handles
res << gen_credential(entry["db"], entry["user"],"fake-password")unless all_ins_users.include?(entry["user"])
end
end
res
rescue Mysql2::Error=> e
@logger.error("MySQL connection failed: [#{e.errno}] #{e.error}")
[]
end
node_ready?
vcap-services-base /lib /base /node.rb
def node_ready?()
# Service Node subclasses can override this method if they depend
# on some external service in order to operate; for example, MySQL
# and Postgresql require a connection to the underlying server.
true
end
encode_XXX
用于将需要发送的信息进行编码。
vcap-services-base /lib /base /node.rb
# Helper
def encode_success(response)
response.success = true
response.encode
end
def encode_failure(response, error=nil)
response.success = false
if error.nil?|| !error.is_a?(ServiceError)
error = ServiceError.new(ServiceError::INTERNAL_ERROR)
end
response.error = error.to_hash
response.encode
end
get_host
返回当前Node主机地址。
vcap-services-base /lib /base /node.rb
def get_host
@fqdn_hosts ?Socket.gethostname :@local_ip
end
第二部分 Service Node实现
1. 自定义Service Node
假如我们要实现一个MyService。
对于Service Node的编写所需要实现那些内容基本上在之前的章节中已经拆散来讲了。可以使用Echo Service的源码为模板进行开发,重点是实现相应的方法。
实现所需文件列表
查看【启动流程】章节说明,Node启动涉及了2个类——NodeBin和Node,所以这里需要两个源码文件分别实现者两个类。在【service_name】一节中提到,惯例上会将service_name方法单独封装,所以这里还需要一个源码文件;此外,在【default_config_file】中提到Node还需要导入配置文件,所以还需要编写一个配置文件。整理后如表9-1所示:
表9-1 Node源码实现所需文件列表
源码文件
说明
示例
NodeBin源码文件
作为Node的启动文件,放在bin目录下
vcap-services /echo /bin /echo_node
Node源码文件
作为Node的核心功能文件,放在lib目录下
vcap-services /echo /lib /echo_service /echo_node.rb
service_name封装
作为一个模块封装,放在lib目录下
vcap-services /echo /lib /echo_service /common.rb
配置文件
放在config目录下
vcap-services /echo /config /echo_node.yml
代码实现
我们以Echo的源码为示例进行分析。
NodeBin类实现
方法汇总
代码示例
代码9.1:vcap-services / echo /bin /echo_node
#!/usr/bin/env ruby #定义使用的解释器
# -*- mode: ruby -*-
#
# Copyright (c) 2009-2011 VMware, Inc.
ENV["BUNDLE_GEMFILE"]||=File.expand_path("../../Gemfile",__FILE__)
require'bundler/setup'
require'vcap_services_base' #需要加载service base库
$LOAD_PATH.unshift(File.expand_path("../../lib",__FILE__)) #将Echo目录下的lib库加入环境变量
require"echo_service/echo_node" #加载Echo的Node类
classVCAP::Services::Echo::NodeBin<VCAP::Services::Base::NodeBin#继承Base::NodeBin
#必须实现:Echo Node类的命名空间。参考【创建一个Echo::Node实例】和【node_class】
def node_class
VCAP::Services::Echo::Node
end
#返回默认配置文件路径,参考【初始化参数配置】和【default_config_file】
def default_config_file
File.join(File.dirname(__FILE__),'..', 'config', 'echo_node.yml')#一般会选择放在config目录下
end
#对于附加参数的处理,参考【addition_config_file】
def additional_config(options, config)
options[:port]= parse_property(config, "port",Integer)
options
end
end
VCAP::Services::Echo::NodeBin.new.start
Node类实现
方法汇总
代码示例
代码9.2:vcap-services / echo /lib /echo_service /echo_node.rb
# Copyright (c) 2009-2011 VMware, Inc.
require"fileutils"
require"logger" #导入日志文件系统,参考【@logger】
require"datamapper"#导入数据库中间件,参考【ProvisionService】
require"uuidtools"
#定义Echo::Node
moduleVCAP
module Services
module Echo
class Node< VCAP::Services::Base::Node#继承自Base::Node
end
end
end
end
require"echo_service/common"#加载service_name方法,参考【service_name】
require"echo_service/echo_error"
classVCAP::Services::Echo::Node
include VCAP::Services::Echo::Common#导入service_name方法
include VCAP::Services::Echo#导入Error处理方法
#定义Service实例表,参考【ProvisionService】
class ProvisionedService
include DataMapper::Resource#导入数据库中间件
property :name,String, :key => true#定义数据库表项
end
#实现Service Node的初始化,参考【启动流程】。
def initialize(options)
super(options) #调用父类中的初始化方法
#这三个参数其实不用这里初始化,在NodeBin的源码中已经实现了,参考【配置参数】
@local_db= options[:local_db]
@port= options[:port]
@base_dir= options[:base_dir]
@supported_versions= ["1.0"]
end
#参考【pre_send_announcement】
def pre_send_announcement
Super #该语句没用
FileUtils.mkdir_p(@base_dir)if @base_dir
start_db #启动数据库
@capacity_lock.synchronizedo #初始化剩余容量
ProvisionedService.all.eachdo |instance|
@capacity-= capacity_unit
end
end
end
#参考【send_node_announcement】
def announcement
@capacity_lock.synchronizedo #返回需要通告的Hash表
{ :available_capacity=> @capacity,
:capacity_unit=> capacity_unit }
end
end
#创建Service实例,参考【主题】和【创建Service实例】
def provision(plan, credential= nil, version=nil)
instance = ProvisionedService.new#创建一个新的数据库表项
if credential #初始化Service实例名字
instance.name = credential["name"]
else
instance.name =UUIDTools::UUID.random_create.to_s
end
begin #保存Service实例
save_instance(instance)
rescue => e1 #在出现错误的情况下,进行恢复,需要销毁创建一半的数据库项
@logger.error("Could not save instance: #{instance.name}, cleanning up")
begin
destroy_instance(instance)
rescue => e2
@logger.error("Could not clean up instance: #{instance.name}")
end
raise e1
end
gen_credential(instance) #需要返回实例的具体认证信息
end
#销毁一个Service实例,参考【销毁Service instance】
def unprovision(name, credentials= [])
return if name.nil?
@logger.debug("Unprovision echo service: #{name}")
instance = get_instance(name)#从数据库中找到该实例
destroy_instance(instance) #销毁该表项
true #返回成功标志
end
#将一个APP与Service实例绑定,参考【APP与Service instance绑定】,注意,在Echo::Node中其实并未实现该方法。
def bind(name, binding_options, credential= nil)
instance = nil
if credential
instance = get_instance(credential["name"])
else
instance = get_instance(name)
end
gen_credential(instance)
end
#将一个APP与Service实例解除绑定,参考【APP与Service instance绑定】,注意,在Echo::Node中其实并未实现该方法。
def unbind(credential)
@logger.debug("Unbind service: #{credential.inspect}")
true
end
#参考【ProvisionService】
def start_db
DataMapper.setup(:default,@local_db)
DataMapper::auto_upgrade!
end
def save_instance(instance)
raise EchoError.new(EchoError::ECHO_SAVE_INSTANCE_FAILED, instance.inspect) unless instance.save
end
def destroy_instance(instance)
raise EchoError.new(EchoError::ECHO_DESTORY_INSTANCE_FAILED, instance.inspect) unless instance.destroy
end
def get_instance(name)
instance = ProvisionedService.get(name)
raise EchoError.new(EchoError::ECHO_FIND_INSTANCE_FAILED,name) if instance.nil?
instance
end
def gen_credential(instance)
credential = {
"host"=> get_host,
"port"=> @port,
"name"=> instance.name
}
end
end
在Echo Service Node的源码中并没有实现管理Serviceinstance的方法。
service_name的封装
vcap-services /echo /lib /echo_service /common.rb
# Copyright (c) 2009-2011 VMware, Inc.
#按照此格式封装即可
moduleVCAP
module Services
module Echo
module Common
def service_name
"EchoaaS"
end
end
end
end
end
配置文件编写
最小配置文件
vcap-services /echo /config /echo_node.yml
---
plan:free
capacity:100
local_db:sqlite3:/var/vcap/services/echo/echo_node.db
mbus:nats://localhost:4222
base_dir:/var/vcap/services/echo/
index:0
logging:
level: debug
pid:/var/vcap/sys/run/echo_node.pid
node_id:echo_node_1
port:5002
supported_versions:["1.0"]
完整配置文件
【--?--还未整理】
- Cloud Foundry Service Node源码分析及实现【附下载】
- Cloud Foundry Service Node源码分析及实现
- Cloud Foundry Service Gateway源码分析
- Cloud Foundry Service Gateway源码分析
- Cloud Foundry新版router源码分析
- cloud foundry之router源码分析
- cloud foundry之dea组件源码分析
- Cloud Foundry中gorouter源码分析
- Cloud Foundry中dea_ng源码文件分析
- Cloud Foundry中gorouter源码分析
- Cloud Foundry中syslog_aggregator的实现分析
- Cloud Foundry中collector组件的源码分析
- Cloud Foundry中Stager组件的源码分析
- PCF (Pivotal Cloud Foundry) Metrics Reference 源码及索引
- Cloud Foundry技术全貌及核心组件分析
- 【Cloud Foundry】Cloud Foundry学习(四)——Service
- cloud foundry dea源码解析
- Cloud Foundry心得及资料
- 画笔的光栅操作
- VC++之图形设备接口------基本概念
- PHP导出MySQL数据到Excel文件
- IW22 BADI:NOTIF_EVENT_SAVE 报Message的问题
- Oracle 11g 新特性之 Oracle Restart Database 的安装与配置完全详解
- Cloud Foundry Service Node源码分析及实现【附下载】
- 规划(2013.1。10)
- poj1125--Stockbroker Grapevine--Floyd算法
- 《卓有成效的管理者》笔记,第二章 掌握自己的时间
- 再论javaIO之拷贝MP3(read方法返回int的原因)
- Java--------数组
- Windows Embedded Compact 7中的内存管理(上)
- 什么时候加上android.intent.category.DEFAULT
- Windows Embedded Compact 7中的内存管理(下)