Cloud Foundry之cloud_controller代码解读

cloud 简介 cloudfoundry是vmvare推出来的开源PaaS平台,cloud_controller是其管理界面的http rest api入口。

补课 rails Rails基于MVC(模型- 视图- 控制器)设计模式。从视图中的Ajax应用,到控制器中的访问请求和反馈,到封装数据库的模型,Rails 为你提供一个纯Ruby的开发环境。
习惯约定大于配置:
model类位置app/models/xxx.rb
controller位置app/controllers/xxx_controller.rc
help位置app/helpers/xxx_helpers.rb
view位置app/views/xxx
对应布局app/views/layouts/xxx.rhtml xxx.rxml

开始cloud_controller https://github.com/cloudfoundry/cloud_controller

里面有两个项目,一个是cloud controller另一个是health_manager,今天要研究的是前者,下节说后者。

入口 bin/cloud_controller -> config/boot.rb
cloud_controller必需要求ruby1.9以上,因为其中有一个fiber需要ruby支持(传说中的Coroutine)。
如果没有配置 cloud_controller使用nginx,则会启用Thin去启动Rack。(Thin是ruby内置的network server,Rack: a Ruby Webserver Interface)

app 以vmc push举例:
vmc push的关键一请求:post apps
对应app/controllers/apps_controller.rb

def create
update_app_from_params(app)
检查各种参数并赋值
app.save! (app使用了ActiveRecord::Base,ActiveRecord是ruby用来做orm的东东,具体可以认为save!就是入库了)
stage_app(app)
去nats注册:result = client.stage(request, AppConfig[:staging][:max_staging_runtime])

关键点 cloud_controller接收到请求记录到db:postgresql/sqlite(cloud_controller.yml中定义)。
请求同时发送到nats等进一步处理。 vmc push所做的事情 1.发一个POST到”apps”,创建一个app; (本文只做了这一步的分析)
2.发一个PUT到”apps/:name/application”,上传app;
3.发一个GET到”apps/:name/”,取得app状态,看看是否已经启动;
4.如果没有启动,发一个PUT到”apps/:name/”,使其启动。

Cloud Foundry之vmc代码解读

cloud 简介 cloudfoundry是vmvare推出来的开源PaaS平台,vmc全称是The VMware Cloud CLI,是vmware的应用平台命令行接口。
ubuntu安装vmc

sudo gem install vmc

常见的用法

vmc target api.paas.n.xiaomi.net
vmc add-user
vmc login
vmc push

vmc其实是使用ruby来实现的一整套的cli命令转化为http restful请求的对应工具。
其代码位于:https://github.com/cloudfoundry/vmc
git clone回来代码之后,下面对代码进行半瓶水解析法解析。

入口 bin/vmc
引用了 $github/vmc/lib/cli
直接运行VMC::Cli::Runner.run
位于:lib/cli/runner.rb

看上去run会被执行。

进入到parse_command!方法中

以push为例:

when 'push'
usage('vmc push [appname] [--path PATH] [--url URL] [--instances N] [--mem] [--runtime RUNTIME] [--no-start]')
if @args.size == 1
set_cmd(:apps, :push, 1)
else
set_cmd(:apps, :push, 0)
end

进入到set_cmd中设置变量

module Cli
:Runner
module Command
:Apps

然后 cmd = VMC::Cli::Command.const_get(@namespace.to_s.capitalize)
cmd.new(@options).send(@action, *@args.collect(&:dup))

最终

VMC::Cli::Command::Apps.new().send(push)
(在ruby语法中,此处send方法是一个关键词,表示callMethod)

于是来到了lib/cli/commands/apps.rb

def push(appname=nil)
def do_push(appname=nil)
upload_app_bits(appname, @application)
def upload_app_bits(appname, path)
client.upload_app(appname, file, appcloud_resources)
lib/vmc/clinet.rb
def upload_app
http_post(path(VMC::APPS_PATH, name, "application"), upload_data)
def http_post(path, body, content_type=nil)
def request(method, path, content_type = nil, payload = nil, headers = {})
perform_http_request
RestClient::Request.execute(req)

于是就没有了(RestClient是ruby中一个rest库)。

Ruby快速学习手记(ubuntu)

ruby ruby 1993年诞生于小。rails是用ruby写的web framework,所谓的ROR正是这一搭档。

ruby安装

ubuntu 10.04很简单,安装ruby和rails:
sudo apt-get install ruby
sudo apt-get install rails

小试牛刀 rails first_rails
cd first_rails/
ruby script/server
浏览器打开127.0.0.1:3000 it's works!

IDE:eclipse

语法特点 解释型
适合文本处理,类似perl
完全面向对象
无分号
方法定义关键字:def+end 木有大括号({})
无需指定类型
局部变量用小写字母或下划线开头即可
全局变量用美元符作为前缀 $;而实例变量用 @ 开头;类变量用 @@ 开头;类名、模块名和常量应该用大写字母开头
常量是以大写字母打头的变量(ruby的常量是可以改变的,但编译器会发出警告。)
ruby中这样实例化类:car = Car.new

Testing and Mocking in spring2.5: Static Class、maven Site、cobertura

mock spring static class cobertura spring 2.5 与 junit 4.4 Spring 2.5.x不能用JUnit 4.5+.如果把使用了spring-test 2.5 的testcase升级到 junit 4.5会得到NoClassDefFoundError:

org.apache.maven.surefire.booter.SurefireExecutionException: org/junit/Assume$AssumptionViolatedException; nested exception is java.lang.NoClassDefFoundError:
org/junit/Assume$AssumptionViolatedException
java.lang.NoClassDefFoundError: org/junit/Assume$AssumptionViolatedException

这是spring-test 2.5.x的一个bug. 其与JUnit 4.5不一致. 只能使用4.0-4.4.
或者就是自己打patch. http://jira.springframework.org/browse/SPR-5145

EasyMock 与 static Class EasyMock未提到是否支持static methods.功能类经常是static的,是因为没有上下文环境依赖,但如果一定要mock他们,可以选择powerMock.

PowerMock、static Class、junit、spring PowerMock 需要你添加 '@RunWith(PowerMockRunner.class)'.但 sping-test 需要你添加 '@RunWith(SpringJUnit4ClassRunner.class)'. http://www.jayway.com/2010/12/28/using-powermock-with-spring-integration-testing/ 如果junit 4.7,PowerMock将RunWith替换为:
@Rule
public PowerMockRule rule = new PowerMockRule();
http://www.infoq.com/news/2009/07/junit-4.7-rules

冲突 Spring 2.5.x不能用JUnit 4.5+, 而PowerMockRule 只能用 JUnit 4.7.
基本上在Java + Spring 2.5 & JUnit 4.4环境下没啥简单的办法去mock static class了.

maven site 与 cobertura 当你运行 'mvn site' or 'mvn sonar:sonar', 在spring项目中你可能会得到:
NoSuchBeanDefinitionException

第一个答案:
在test的application文件中添加:

  1. <aop:config proxy-target-class="true"/>  

第二个答案:
'mvn site' 与 'mvn sonar:sonar' 会生成类在目录 ./generated-classes/cobertura 中.确保你想autowire的类都在正确的目录下扫描.

-----------

spring 2.5 and junit 4.4 Spring 2.5.x is incompatible with JUnit 4.5+.If you upgrade to junit 4.5,all of your test case which uses the SpringJUnit4ClassRunner will get a NoClassDefFoundError:

org.apache.maven.surefire.booter.SurefireExecutionException: org/junit/Assume$AssumptionViolatedException; nested exception is java.lang.NoClassDefFoundError:
org/junit/Assume$AssumptionViolatedException
java.lang.NoClassDefFoundError: org/junit/Assume$AssumptionViolatedException

It is an known issue with spring-test 2.5.x. It is incompatible with JUnit 4.5. Use 4.0-4.4.
Or you can try the patch in the issue tracker. http://jira.springframework.org/browse/SPR-5145

EasyMock and static Class EasyMock doesn't mention static methods.Utility methods should be most of the time static as they have no context and no dependencies.But when you need to mock it,you can you choose PowerMock.

PowerMock、static Class、junit、spring PowerMock need you add '@RunWith(PowerMockRunner.class)' to your test code.But sping-test need you add '@RunWith(SpringJUnit4ClassRunner.class)'. http://www.jayway.com/2010/12/28/using-powermock-with-spring-integration-testing/ With junit 4.7,PowerMock can replace RunWith to:
@Rule
public PowerMockRule rule = new PowerMockRule();
http://www.infoq.com/news/2009/07/junit-4.7-rules

conflict Spring 2.5.x is incompatible with JUnit 4.5+, and that PowerMockRule is compatible with JUnit 4.7.
Basically, There isn't an easy way to do this in Java + Spring 2.5 & JUnit 4.4 at the moment.

maven site and cobertura When you run 'mvn site' or 'mvn sonar:sonar' with spring project,you may get the error:
NoSuchBeanDefinitionException

The first answer:
In your test application context add the line:

  1. <aop:config proxy-target-class="true"/>  

The second answer:
The 'mvn site' or 'mvn sonar:sonar' will generates class in ./generated-classes/cobertura.Make sure the classes that you want to autowire is all scan in right folder.

利用h2database和easymock轻松不依赖环境单元测试(rose等spring环境万能)

h2database,easymock 前言 写java时间久了,慢慢就变得自信了,也可能是变得懒了,或者是项目进度的原因,test case越写越少,越来越不写了。
还有一个很大的原因是,DB环境不好搞,老是有垃圾数据,依赖的东西太多,有memcache、有别人的服务,等等等等,都是没有test case良好的理由。

下面将以rose环境下,使用easy mock搞写第三方依赖和h2database搞写DB环境为例子,简单讲述如何轻松测试。

完整代码可以在 https://github.com/XiaoMi/rose 下载到。

依赖的包介绍 spring-test:提供一些基础的spring环境的支持,如果你不是spring环境,可能不需要。
h2:一个内存数据库,使用它的原因是,我们的业务大多数时候使用的都是mysql,而h2有一个mysql模式,可以支持绝大多数的mysql的SQL语句。
easymock:以前在五四陈科学院提到过的利器,用来模拟各种调用的工具。

pom定义为:

  1.       <dependency>  
  2.     <groupId>org.springframework</groupId>  
  3.     <artifactId>spring-test</artifactId>  
  4.     <version>2.5.6</version>  
  5.     <scope>test</scope>  
  6. </dependency>  
  7. <dependency>  
  8.     <groupId>com.h2database</groupId>  
  9.     <artifactId>h2</artifactId>  
  10.     <version>1.3.163</version>  
  11.     <scope>test</scope>  
  12. </dependency>    
  13. <dependency>  
  14.     <groupId>org.easymock</groupId>  
  15.     <artifactId>easymockclassextension</artifactId>  
  16.     <version>2.4</version>  
  17.     <scope>test</scope>  
  18. </dependency>  

基础配置与schema.sql准备 在test/resources下我们需要两个关键的文件:applicationContext.xml 与 schema.sql

applicationContext.xml定义了test执行时扫描的情况,同正常运行时使用的配置大同小异。不同在于需要替换db定义中的mysql driver为h2的driver,关键部分:

  1. <property name="driverClassName" value="org.h2.Driver"></property>  
  2. <property name="url" value="jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1"></property>    
而schema.sql文件定义了初始化的表结构,这个文件的出现是因为h2与mysql还是有一定的语法区别的,不过在我的使用过程中,基本上只有create table的那个引擎声明是不支持的,所以喽,干掉先。

H2下的DAO的Test的代码示例 在test case的初始化状态里需要导入schema.sql,代码如下:

  1. Statement st = conn.createStatement();  
  2.          st.execute("drop all objects;");  
  3.          st.execute("runscript from '" + new DefaultResourceLoader().getResource("schema.sql").getURL().toString() + "'");  
  4.          st.close();  
而如果是spring/rose环境,则可以再加载上context.xml文件,代码如下:
  1. @RunWith(SpringJUnit4ClassRunner.class)  
  2. @ContextConfiguration(locations = "classpath:applicationContext.xml")  
  3. public class TestDAOTest {  
然后就开始写测试代码:
  1. @Test  
  2.     public void getTestTest() {  
  3.         com.chen.model.Test t = testDAO.getTest();  
  4.   
  5.         Assert.assertEquals(111, t.getId());  
  6.     }  
easymock的代码示例

easymock以前也提到过,这里再提一次:http://www.54chen.com/java-ee/spring-easymock-tutorial.html

  1. @Test  
  2.   public void getTestTest() {  
  3.       com.chen.model.Test t = new com.chen.model.Test();  
  4.       t.setId(1111);  
  5.       t.setMsg("adfadf");  
  6.       TestDAO testDAO = EasyMock.createMock("testDAO", TestDAO.class);  
  7.       EasyMock.expect(testDAO.getTest()).andReturn(t);  
  8.       EasyMock.replay(testDAO);  
  9.       ReflectionTestUtils.setField(service, "testDAO", testDAO, TestDAO.class);  
  10.       t = service.getTest();  
  11.       // 确信使用了mock  
  12.       EasyMock.verify(testDAO);  
  13.       Assert.assertEquals(1111, t.getId());  
  14.   }  

mock其他东西以此类推。

后序 至此,已经可以mock一切了,你还有什么理由不写test case呢?
本文完整代码可以在 https://github.com/XiaoMi/rose 下载到。

Zookeeper客户端curator使用手记

zookeeper 一、简介 curator是Netflix公司开源的一个Zookeeper client library,用于简化zookeeper客户端编程,包含一下几个模块:

curator-client - zookeeper client封装,用于取代原生的zookeeper客户端,提供一些非常有用的客户端特性
curator-framework - zookeeper api的高层封装,大大简化zookeeper客户端编程,添加了例如zookeeper连接管理、重试机制等
curator-recipes - zookeeper recipes 基于curator-framework的实现(除2PC以外)

从github和maven上的消息来看,1.0.1的版本已经十分稳定,相对应的zk版本是3.3.x,还在开发中的版本是1.1.x,对应的版本是zk3.4.x。 二、依赖

  1. <dependency>  
  2.             <groupId>com.netflix.curator</groupId>  
  3.             <artifactId>curator-framework</artifactId>  
  4.             <version>1.0.1</version>  
  5.         </dependency>  

三、代码讲解

以下代码以CuratorFramework为例:

  1. public static void main(String[] args) throws Exception {  
  2.      String path = "/test_path";  
  3.      CuratorFramework client = CuratorFrameworkFactory.builder().connectString("zookeeper.n.miliao.com:2181").namespace("/brokers").retryPolicy(new RetryNTimes(Integer.MAX_VALUE, 1000)).connectionTimeoutMs(5000).build();  
  4.      // 启动 上面的namespace会作为一个最根的节点在使用时自动创建  
  5.      client.start();  
  6.   
  7.      // 创建一个节点  
  8.      client.create().forPath("/head"new byte[0]);  
  9.   
  10.      // 异步地删除一个节点  
  11.      client.delete().inBackground().forPath("/head");  
  12.   
  13.      // 创建一个临时节点  
  14.      client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/head/child"new byte[0]);  
  15.   
  16.      // 取数据  
  17.      client.getData().watched().inBackground().forPath("/test");  
  18.   
  19.      // 检查路径是否存在  
  20.      client.checkExists().forPath(path);  
  21.   
  22.      // 异步删除  
  23.      client.delete().inBackground().forPath("/head");  
  24.   
  25.      // 注册观察者,当节点变动时触发  
  26.      client.getData().usingWatcher(new Watcher() {  
  27.          @Override  
  28.          public void process(WatchedEvent event) {  
  29.              System.out.println("node is changed");  
  30.          }  
  31.      }).inBackground().forPath("/test");  
  32.   
  33.      // 结束使用  
  34.      client.close();  
  35.  }  

四、方法说明 create(): 发起一个create作. 可以组合其他方法 (比如mode 或background) 最后以forPath()方法结尾
delete(): 发起一个删除作. 可以组合其他方法(version 或background) 最后以forPath()方法结尾
checkExists(): 发起一个检查ZNode 是否存在的作. 可以组合其他方法(watch 或background) 最后以forPath()方法结尾
getData(): 发起一个获取ZNode数据的作. 可以组合其他方法(watch, background 或get stat) 最后以forPath()方法结尾
setData(): 发起一个设置ZNode数据的作. 可以组合其他方法(version 或background) 最后以forPath()方法结尾
getChildren(): 发起一个获取ZNode子节点的作. 可以组合其他方法(watch, background 或get stat) 最后以forPath()方法结尾
inTransaction(): 发起一个ZooKeeper事务. 可以组合create, setData, check, 和/或delete 为一个作, 然后commit() 提交

五、五四陈点评

相比zookeeper常用的zkClient的确是平易近人了,上手容易,理解轻松,语法优美,至于是不是各种情况都处理了,还得靠实践检验。