在虚拟机里安装openstack

在VirtualBox的虚拟机里面按照这个文档把openstack装了一把。

准备工作:

  1. 创建虚拟机server1和server2,Ubuntu 64位
  2. 两块网卡,eth0 NAT,eth1 Internal Network
  3. server1分配两块硬盘,第二块专作nova的volume(需要特别指出,openstack的文档对这里没作任何说明,我是做到那步的时候才发现,sda6是神马东西!当然也可以单独分个区,我是没办法了才挂上一块新的硬盘,得亏是虚拟机)
  4. 安装OS,没有特别的

然后就是安装文档一步步下去。大部分地方都完全没有问题,有几点需要说一下。

1. 网络配置

  • VirtualBox虚拟出来的网关是10.0.2.2,不是10.0.2.1。
  • VirutalBox NAT模式下,两个虚拟机之间实际上是不通的,所以在server2上配置ntp以及nova的时候一定要使用192.168.3.1来连接server1。这一点实际上官方文档写得并不好,已经有了内部网络,为什么还要使用外部的呢?无论是从security还是从逻辑上都说不通。

server1:

# The loopback network interface

auto lo

iface lo inet loopback

# The primary network interface

auto eth0

iface eth0 inet static

address 10.0.2.14

netmask 255.255.255.0

broadcast 10.0.2.255

gateway 10.0.2.2

auto eth1

iface eth1 inet static

address 192.168.3.1

netmask 255.255.255.0

network 192.168.3.0

broadcast 192.168.3.255

server2

# The loopback network interface

auto lo

iface lo inet loopback

# The primary network interface

auto eth0

iface eth0 inet static

address 10.0.2.15

netmask 255.255.255.0

broadcast 10.0.2.255

gateway 10.0.2.2

auto eth1

iface eth1 inet static

address 192.168.3.2

netmask 255.255.255.0

network 192.168.3.0

broadcast 192.168.3.255

2. 数据库

为了不跟Oracle扯到一起,没有用MySQL,安装文档上的步骤PostgreSQL完全没有问题。

3. ntp一定要配置正确,server2需要跟server1同步

ntp我不是很懂,配好之后怎么都不同步,把ntpd停掉之后手工跑了一把ntpdate 192.168.3.1,才搞定。应该是我哪里配置的不对。

4. 重启service

诡异得很,sudo restart nova-xxx或者sudo service nova-xxx restart经常不工作,用最土的方法解决:sudo /etc/init.d/nova-xxx restart

5. server2上面需要安装PostgreSQL的python module

sudo apt-get install python-psycopg2

否则nova-compute起不来

6. 试图安装Horizon,未果

Ubuntu上有openstack-dashboard这个包,装好之后按照这里配置,还是没起来,应该是python的virtual env没有弄,不懂。

反正Horizon还在猛烈开发中,不怎么稳定,功能也有限,暂时不搞了吧。

7. 貌似server2根本不是必须的

估计就是为了nova-compute自己的分布,对于管理少量的虚拟机来说,一台机器应该足够了。

宽带升级光纤

一直以为住的小区很土,没有光纤,最近意外接到电信电话,说是要升级了,2M变4M不用加钱。升!

家里的光纤出口在外间屋,二奶机在里间,整个挪出来工程太大,而且没地方放,小P随时可能把二奶机砸坏。同事推荐买了电力猫,285,小贵。

晚上回家折腾,问题开始冒出来了。

  1. 两个电力猫始终无法互联。google了半天,才发现是pair那个按钮按的时间太长了。应该按5秒钟的样子就松开,这样电源灯闪烁并开始配对。说明书些得太烂了。可能光盘上的好一些,可是是小盘,还得把外置光驱拿出来,麻烦。
  2. 光猫没有管理员权限,默认只给了useradmin。同事说可以找安装的小工要,开始装的时候我不在家,后来打电话过去,关机了,估计下班之后就不接电话了。还是求助于万能的google,发现我这个光猫的型号太容易找出密码了。这个也很快搞定。
  3. 如何把光猫变成纯粹的拨号器。其实倒不复杂,只是不确定之前adsl的用户名密码现在还能不能用,网上有人说是一样的,有人说是不一样的,纠结。最后痛下决心,删除已有的配置,改成桥接模式,在路由器上拨号。还好,密码没变,成功了。
  4. 之前还尝试过DMZ,不过没搞定,更可怕的是光猫上红灯亮起,告警!别这么寸,刚装好就搞坏了。算了,放弃。

没有IPTV的需求,也没有VoIP的需求,剩下那两个连接就留在那里吧。还有一个问题,远程管理不知道怎么关掉,也不知道能不能关掉,万一哪天电信抽风连上来把我的配置删了就完蛋了。如果光猫被打上patch,拿不到管理员密码就更麻烦了。

Scala与PowerMock

这个题目对搜索引擎应该是比较友好的,虽然土了些。

PowerMock是个好东西,在某种程度上拯救了架构设计,使得我们不用过分考虑如何用复杂的架构设计来达到可以单元测试的目的。

Java里面如何使用PowerMock就不用多说了,随便google一下满屏幕都是,如果是同事还可以跟我要codekata的材料。但是这事儿换成Scala,情况就完全不同了。

首先要解决的问题是Scala用什么测试框架。比较流行的有ScalaTestSpecs,二者都支持BDD、ATDD,也就是这个BDD、ATDD把我折磨了一个晚上。

撇开ATDD不谈,我非常想认真尝试一下BDD,但是ScalaTest的BDD需要使用它自己的Runner,Specs的虽然支持JUnit,但是也需要用一个特殊的Runner,这对于PowerMock来说几乎是致命的。PowerMock+Junit,必须使用PowerMockRunner才能mockStatic,mockNew,PowerMock+TestNG倒是不需要Runner,但是ScalaTest不支持用TestNG来做BDD,而Specs根本就不支持TestNG。

有点儿混乱,总之一句话,现阶段,Scala+BDD+PowerMock这条路完全走不通。如果我的判断有问题,望指正。

那么就退一步,还是JUnit。从一个例子来说明我遭受到的痛苦以及整个解决过程。

1
2
3
4
5
6
7
8
9
10
11
12
public class MyStatic {
    public static String hello() {
        // connect to twitter for example
        return tweet;
    }
}
 
public class HelloWorld {
    public String helloWorld() {
        return MyStatic.hello();
    }
}

上面一段代码,Java+PowerMock很容易处理:

1
2
3
4
5
6
7
8
9
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyStatic.class)
public HelloWorldTest {
    @Test
    public testHelloWorld() {
        mockStatic(MyStatic.class);
        ....
    }
}

如果是Scala,问题就接踵而至了。先看功能相似的Scala代码片段:

1
2
3
4
5
6
7
8
9
10
object MyStatic {
  def hello = {
    // connect to twitter for example
    tweet
  }
}
 
class HelloWorld {
  def helloWorld() = MyStatic.hello
}

@RunWith没有问题:

1
@RunWith(classOf[PowerMockRunner])

@PrepareForTest就开始出状况了。

首先貌似Scala对annotation的解释有些不太好,不能简单地传一个东西给@PrepareForTest,因为这玩意儿有两个参数,每个都是数组

1
2
3
4
5
6
7
8
9
@Target( { ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PrepareForTest {
    Class[] value() default IndicateReloadClass.class;
 
    String[] fullyQualifiedNames() default "";
}

所以不能:

1
@PrepareForTest(classOf[SomeClass])

必须要:

1
@PrepareForTest(Array(classOf[SomeClass]))

这个还算是比较容易解决,但是对于Scala里的object,情况又有变化。object不支持classOf这个运算符,classOf[MyStatic]是非法的;MyStatic.getClass倒是可以拿到类,但这个类是另一个,名字是MyStatic$(后面会详细解释这两个类以及它们之间的关系)。但即使getClass得到的类是正确的,也没法直接传给annotation,因为annotation要求参数必须是一个constant,不能是一个方法调用,因此@PrepareForTest(Array(MyStatic.getClass))也是不行的。

怎么办呢?还好@PrepareForTest还有第二个参数,我们可以这样:

1
@PrepareForTest(fullyQualifiedNames = Array("com.honnix.test.MyStatic"))

虽然比较恶心,而且不利于重构,但是聊胜于无吧。

然后就是mockStatic的问题。怎么写呢?

1
mockStatic(MyStatic.getClass)

这肯定不行,因为之前说了getClass得到的类是MyStatic$。那么怎么样才能mock MyStatic呢?答案就是,反射。

1
mockStatic(Class.forName("com.honnix.test.MyStatic"))

没有其他任何办法得到一个object的class信息,这里有一个很古老的bug

好吧,两个问题都“解决”了,跑跑看。结果却是我想mock的东西根本就没有被mock。要命了。

事情还得从头说起。Scala对于一个object编译的结果是生成两个类,一个MyStatic,一个MyStatic$:

1
2
3
4
5
6
7
public final class MyStatic
{
  public static final String hello()
  {
    return MyStatic$.MODULE$.hello();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class MyStatic$
  implements ScalaObject
{
  public static final  MODULE$;
 
  static
  {
    new ();
  }
 
  public String hello()
  {
    return "hello";
  }
 
  private MyStatic$()
  {
    MODULE$ = this;
  }
}

这是反汇编的结果,稍微有点乱,凑合着看吧。不用多解释,代码已经很明白了。

问题是,我对MyStatic进行mock,理论上说也应该可以的,也就是mock之后就无视MyStatic$了。为什么不行呢?还得来看看调用地方的反汇编代码:

1
2
3
4
5
public testHelloWorld {
    ...
    MyStatic$.MODULE$.hello();
    ...
}

这里面压根儿就没有MyStatic的事儿了!MyStatic被华丽的忽视了!

到此为止终于真相大白了。咱们得这样来mock。

1
2
3
4
5
6
7
8
9
10
@RunWith(classOf[PowerMockRunner])
@PrepareForTest(fullyQualifiedNames = Array("com.honnix.test.MyStatic$"))
class HelloWorldTest {
  @Test
  def testStartAndStop() {
    val mockMyStatic = mock(MyStatic.getClass)
    Whitebox.setInternalState(MyStatic.getClass, mockMyStatic)
    ...
  }
}

想法很简单,把MyStatic$里面的MODULE$给换喽。

代码很丑陋,所以需要强调一下PowerMock应该被用在那些确实不需要复杂的依赖注入的地方来帮助简化架构设计,对于需要做依赖注入的地方,我们还是绝对不能松懈的,不能因为PowerMock啥都能mock就乱写代码。

顺便说一句,Specs对于Mockito已经有很好的支持了,加上Scala那种类似DSL的表达式,用起来很爽。希望PowerMock的作者能考虑支持Scala。

Java调用Scala的一个真实案例

Scala调用Java,完全没有问题;但是反过来总有那么一点不爽,或者必须要修改。这里是项目中一个真实的案例,拿出来分享一下。

1. 类型转换

1
2
3
4
5
object AsnLoader {
  ...
  def batchLoadFile(files: List[String]): Map[String, AsnModule] = files.foldLeft(Map[String, AsnModule]())((x, y) => x ++ load(y))
  ...
}

这里的List是Scala里的类,从Java里面调这个方法是不行的,所以需要弄一个wrapper方法:

1
2
3
import collection.JavaConversions._
...
def batchLoadFile(files: java.util.List[String]): java.util.Map[String, AsnModule] = batchLoadFile(asScalaBuffer(files).toList)

虽然asScalaBuffer本身就是个implicit,但是这里仍需要显示调用一下,否则编译器会认为batchLoadFile(file: java.util.List[String])更符合调用匹配规则,结果就变成了无限递归……

Java里面这样调用:

1
AsnLoader.batchLoadFile(list);

这样看起来解决问题了,但是Eclipse的Scala插件会报一个莫名其妙的函数签名错误,其实根本就没错,直接maven或者用Intellij都是对的,Eclipse插件太烂或者太老。无奈只好把函数名也一起改了:batchLoadFileJ,这样就可以了。如果还不是很明白,去看看JavaConversions里面的implicit。

把Java的Map转换成Scala的immutale map稍微曲折了些,先转换成mutable map,再调用toMap方法转换成immutable map。

2. 构造方法

1
2
3
4
5
class DefaultTransformer(private val asnModule: AsnModule, private val dependencies: Map[String, AsnModule] = Map.empty,
                         private val repository: Map[String, AsnModule] = Map.empty,
                         private val messageHandler: MessageHandler = ConsoleMessageHandler(false)) {
  ...
}

这里面的Map都是Scala的Map,所以从Java里不能直接调用,而Scala的构造方法又比较特别,重载的话只能减少参数个数而不能改变类型。这时候就需要companion object上场了。

1
2
3
4
5
6
object DefaultTransformer {
  def apply(asnModule: AsnModule,
                 dependencies: java.util.Map[String, AsnModule],
                 repository: java.util.Map[String, AsnModule],
                 messageHandler: MessageHandler) = new DefaultTransformer(asnModule, dependencies, repository, messageHandler)
}

Java这样调用:

1
Transformer transformer = DefaultTransformer.apply(...);

3. Scala的bug

嗯,XML在什么语言里都是让人恶心的东西,即使Scala已经尽力做好了:https://issues.scala-lang.org/browse/SI-4865

单元测试拯救了架构设计?

经常听到这样一种说法:单元测试迫使你不得不进行仔细的架构设计。

这说法没错,如果没有好的架构设计,代码几乎是不能做单元测试的:到处都在new,静态方法随手调用,等等等等,如此这般。

但通常这样做的结果是引入了不必要的过度设计。本来new一个对象就用了,现在要做依赖注入,无论是用spring之类的基于xml的配置注入,还是guice那样的annotation注入,总之一但有了这些,代码几乎就变得不可读了,因为你从代码逻辑里你根本看不出某个对象是什么,是怎么构造出来的;为了弄明白一段代码究竟是怎么工作的,你需要找到做依赖注入的地方:xml或者annotation,或者随便什么。总之如果没有一个好的IDE帮你在各种不同的文件中穿梭,没有一个详细的设计文档,你需要相当长的时间来整明白一个前辈设计的优秀系统是如何被粘在一起工作的。

也许你同样有优于常人的头脑、敏锐的洞察力,可以在很短的时间内把东西看懂,然后继续开发,但并不是所有人都能做到这一点。这样问题就来了,一个所谓优秀的架构设计能维持多久?也许换了几拨人之后,最初的设计思想就完全没人能理解了,然后新人会在代码里恶狠狠地写下shit之类的注释,或者把看不懂的代码拷贝一份出来修修补补,或者经过一番痛苦的思想斗争之后完全重写,替换成他所熟悉的依赖注入框架。天呐,这是在做优秀的架构设计吗?不觉得是在自掘坟墓吗?

先别准备开口反驳,或者破口大骂,我一点儿反对依赖注入的意思都没有。我非常赞成在系统关键部位使用依赖注入,特别是那些模块接口的地方。这就像中医的针灸,针总是要扎在关键的位置,而不是插满全身,前者治病救人,后者纯粹就是解恨。

而选择依赖注入的方式,能用annotation就别用xml。推崇xml的人最喜欢说的一句话就是:用xml配置一下,可以修改系统行为而不用重新编译,特别是对于Java这样的静态语言。不错,xml是可以做到这一点,但是仔细回想一下,究竟有多少次我们这样干过?究竟有多少是真正的需求,而不是开发人员或者系统设计师一厢情愿的天真想法?注意,我们是在谈依赖注入的配置,而不是单纯的参数配置。必须弄明白的是,用作依赖注入的xml也是代码的一部分,也就是说,如果脱离了xml只看代码,你压根儿就读不懂。而且xml这玩意儿信息冗余得很,你得练就一双法眼,透过tag看本质。顺便说一句,Java里面的xml实现是我见过的最恶心的设计,简直可以作为Java语言使用的优秀典范,因为它可能用到了所有的Java语言特性。

同样的过度设计问题在AOP里也存在,不过这可能完全是我的个人喜好,可以任意批判。我根本无法理解为什么为了省掉一些异常处理的代码,而引入一堆一堆屎一样的xml配置。你可以说这是为了提炼出系统的本质,让人很容易看懂代码逻辑是什么而不用纠缠于异常情况,但是哥们儿,代码里50%以上都是在处理异常,异常根本就是逻辑的一部分,作为一个开发或者维护人员,我需要知道异常时候究竟干了什么,而不是忽略。如果需要业务人员也能很容易明白代码做了什么,我们需要的是DSL,而不是AOP。搞Java的人就喜欢弄出一个个看起来很酷的缩写,AOP,SOA,JEE,JSE,EJB,JCA,JAXP,JAXB,JMX,JMS……我已经晕了,不过也没什么,也就是一帮人躲在屋子里折腾出来的一些规范而已。

在需要依赖注入的地方用annotation的方式注入,在该简洁的地方毫不犹豫地直接调用,允许一定的代码冗余,把架构设计做得让中等水平的人可以很快看懂并且继续开发,让水平次点儿的人也能明白是怎么回事儿,这样的系统也许会具有更强的生命力。

但是一些耦合度很强的类怎样做单元测试呢?其实在Java世界里这个问题早就解决了,有一票儿的单元测试mock工具:JMock,Mockito,Powermock,等等。别再纠结于“做个依赖注入吧,否则怎么做单元测试”这种问题了,依赖注入不是为了解决如何做单元测试的,不能为了单元测试刻意做一些不必要的复杂设计,因为单元测试可能也就是芝麻而已。

JRuby和Java的互操作

我试图让新的BL框架支持所有流行的JVM语言。Scala很简单,天衣无缝,但是Ruby和Python就比较周折了。不谈Python,先看看Ruby,确切地说是JRuby。

jrubyc可以把一个Ruby的class编译成Java的,看看下面的例子:

Bar.java

1
2
3
4
5
package com.honnix.foo;
 
interface Bar {
    int getValue();
}

BarImpl.rb

1
2
3
4
5
6
7
8
9
10
11
java_package 'com.honnix.foo'
require 'java'
 
class BarImpl
  include Java::com.honnix.foo.Bar
 
  java_signature 'int getValue()'
    def get()
      1
    end
end

> javac Bar.java
> jrubyc --javac -cp com.honnix.foo BarImpl.rb

这样基本上就差不多了。

不过看看生产的Java代码,就知道这样玩儿的效率恐怕成问题。其实就是先build出来一个Ruby代码的巨大字符串,然后给交给Ruby引擎去跑,最后返回结果。

不管怎么说,只是这是一个方案,接下来就是要考虑怎么把Java的Document转成Ruby喜欢的格式,再转回来。

Java的范型方法

Java的范型一直让我头疼,也一直没仔细研究,总之感觉不好用。这两天折腾了一下,把范型方法整明白了。

一个简单的例子:

1
2
3
4
private static <T> T test(T a) {
    Person<T> person = new Person<T>(a);
    return person.getName();
}

调用的时候这样:

1
test(1);

是不是认为编译器编译的时候会自动将T转换成Integer?我们来看看javap的结果:

1
invokestatic	#22; //Method test:(Ljava/lang/Object;)Ljava/lang/Object;

看到什么了?没有Integer,只有Object!

如果这样设计test():

1
2
3
4
private static <T extends Number> T test(T a) {
    Person<T> person = new Person<T>(a);
    return person.getName();
}

结果就是:

1
invokestatic	#23; //Method test:(Ljava/lang/Number;)Ljava/lang/Number;

所以是不是可以得出结论,javac根本就不看调用的时候传入什么类型的东西,只看方法定义?

你完全可以这样写:

1
2
3
4
private static Object test(Object a) {
    Person<Object> person = new Person<Object>(a);
    return person.getName();
}

只是前者看上去比较牛逼罢了,其实效果完全相同。

maven-assembly-plugin处理依赖的时候感觉不大对头

按照maven推荐的方式组装binary:

  1. 建一个子module
  2. packaging类型为pom
  3. 依赖需要被组装的子module
  4. <useAllReactorProjects>true</useAllReactorProjects>

但是在写assemble文件的时候,有个诡异的地方:似乎moduleSet的dependencySet并不是选择的某个module所依赖的。

对于cheater-node的总结——underscore篇

javascript确确实实是一个functional的语言,但问题是它本身并没有提供什么方便使用的库。不过还好我们有社区,有无数英雄们默默无闻地做贡献(眼泪花花儿的)。

介绍一下underscore。点过去看看就知道了,亲切得很。

安装很简单,直接npm就行了。不知道npm是什么的,或者google,或者参考这里

虽然那里有无数的例子,但这里还是给一个吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
var loginInfo = function () {
    var info = [
        ['tz_offset', '480'],
        ['username', 'xxx'],
        ['password', 'yyy'],
        ['realm', 'Zoning']
    ];
    var _ = require('underscore');
 
    return _(info).map(function (item) {
        return item[0] + '=' + item[1];
    }).join('&amp;');
};

“_(info)”返回一个对象,包含一个名叫_wrapped的property。

> var _ = require('underscore');
> var w = _([1, 2, 3]);
> w
{ _wrapped: [ 1, 2, 3 ] }

有了这个东西之后一切都变得美好了,跟所有functional的用法一样,点啊点得就干了很多事儿。不过需要注意的是_(info).map()之后返回的东西就是一个数组了。嗯,不太好……

这个问题人家早就考虑过了:

1
2
3
4
5
6
7
8
9
10
11
12
13
var loginInfo = function () {
    var info = [
        ['tz_offset', '480'],
        ['username', 'xxx'],
        ['password', 'yyy'],
        ['realm', 'Zoning']
    ];
    var _ = require('underscore');
 
    return _(info).chain().map(function (item) {
        return item[0] + '=' + item[1];
    }).join('&amp;').value();
};

先chain一下,玩儿完之后再value一把。这回爽了吧?

顺便说一下,functional这么好的东西为什么会有人跳出来反对呢?

再多废一句话啊。如果你在node的交互式终端里面使用“_”,你会发现诡异的问题,原因是这个终端默认使用“_”表示上一个命令的返回值。

对于cheater-node的总结——继承篇

javascript的对象模型诡异得很,我等半脑残几乎无法领会其精要,仅继承这件简单的事情就有无数种实现方法。不过既然用nodejs,就尽量按照它的标准来走。

Cheater这个类需要触发以及监听事件,所以需要继承EventEmitter这个类。

1
2
3
4
5
6
7
8
var events = require('events');
 
var Cheater = function () {
    events.EventEmitter.call(this);
    ...
};
 
require('sys').inherits(Cheater, events.EventEmitter);

代码倒是简单得很,不过背后还是做了很多事情的。这里写得很详细,就不废话了:http://blog.nodejitsu.com/using-sys-inherits-in-node-js

 

更新:sys这个module在nodejs 0.3之后的版本改成了util,为了保持兼容,sys仍可以用,但是不推荐。