2015年10月15日星期四

理解 OpenStack + Ceph (5):OpenStack 与 Ceph 之间的集成 [OpenStack Integration with Ceph] - SammyLiu

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
理解 OpenStack + Ceph (5):OpenStack 与 Ceph 之间的集成 [OpenStack Integration with Ceph] - SammyLiu  阅读原文»

理解 OpenStack + Ceph 系列文章:

(1)安装和部署

(2)Ceph RBD 接口和工具

(3)Ceph 物理和逻辑结构

(4)Ceph 基础数据结构

(5)Ceph 与 OpenStack 的集成

1. Glance 与 Ceph RBD 集成

1.1 代码

Kilo 版本中,glance-store 代码被从 glance 代码中分离出来了,地址在 https://github.com/openstack/glance_store

Glance 中与 Ceph 相关的配置项:

配置项含义默认值
rbd_pool保存rbd 卷的ceph pool 名称images
rbd_userrbd user id,仅仅在使用 cephx 认证时使用none。让 librados 根据 ceph 配置文件决定
rbd_ceph_confCeph 配置文件的完整路径/etc/ceph/ceph.conf
rbd_store_chunk_size卷会被分成对象的大小(单位为 MB)64
Glance_store 使用 rbd 和 rados 模块:
import rados
import rbd
Glance_store 中新增了文件 /glance_store/_drivers/rbd.py,其中实现了三个类:
  • class StoreLocation(location.StoreLocation) #表示 Glance image 在 RBD 中的location,格式为 "rbd://<FSID>/<POOL>/<IMAGE>/<SNAP>"
  • class ImageIterator(object) #实现从 RBD image 中读取数据块
  • class Store(driver.Store): #实现将 RBD 作为Nova 镜像后端(backend)

class Store(driver.Store) 实现的主要方法:

(1)获取 image 数据:根据传入的Glance image location,获取 image 的 IO Interator

def get(self, location, offset=0, chunk_size=None, context=None):
return (ImageIterator(loc.pool, loc.image, loc.snapshot, self), self.get_size(location))
1. 根据 location,定位到 rbd image
2. 调用 image.read 方法,按照 chunk size 读取 image data
3. 将 image data 返回调用端

(2)获取 image 的 size

def get_size(self, location, context=None):
1. 找到 store location:loc = location.store_location
2. 使用 loc 中指定的 pool 或者配置的默认pool
3. 建立 connection 和 打开 IO Context:with rbd.Image(ioctx, loc.image, snapshot=loc.snapshot) as image
4. 获取 image.stat() 并获取 “size”

(3)添加 image

def add(self, image_id, image_file, image_size, context=None):
1. 将 image_id 作为 rbd image name:image_name = str(image_id)
2. 创建 rbd image:librbd.create(ioctx, image_name, size, order, old_format=False, features=rbd.RBD_FEATURE_LAYERING)。如果 rbd 支持 RBD_FEATURE_LAYERING 的话,创建一个 clonable snapshot:rbd://fsid/pool/image/snapshot;否则,创建一个 rbd image:rbd://image
3. 调用 image.write 方法将 image_file 的内容按照 chunksize 依次写入 rbd image
4. 如果要创建 snapshot 的话,调用 image.create_snap(loc.snapshot) 和 image.protect_snap(loc.snapshot) 创建snapshot

(4)删除 image

def delete(self, location, context=None):
1. 根据 location 计算出 rbd image,snapshot 和 pool
2. 如果它是一个 snapshot (location 是 rbd://fsid/pool/image/snapshot),则 unprotect_snap,再 remove_snap,然后删除 image;这时候有可能出错,比如存在基于该 image 的 volume。
3. 如果它不是一个 snapshot (location 是 rbd://image),直接删除 rbd image。这操作也可能会出错。

1.2 使用

(1)使用 Ceph RDB 作为后端存储创建一个 Glance image:
root@controller:~/s1# glance image-show 71dc76da-774c-411f-a958-1b51816ec50f
+------------------+----------------------------------------------------------------------------------+
| Property | Value |
+------------------+----------------------------------------------------------------------------------+
| checksum | 56730d3091a764d5f8b38feeef0bfcef |
| container_format | bare |
| created_at | 2015-09-18T10:22:49Z |
| direct_url | rbd://4387471a-ae2b-47c4-b67e-9004860d0fd0/images/71dc76da-774c- |
| | 411f-a958-1b51816ec50f/snap
可见该Glance image 在 Ceph 中其实是个 RBD snapshot,其父 image 为 rbd://4387471a-ae2b-47c4-b67e-9004860d0fd0/images/71dc76da-774c-411f-a958-1b51816ec50f/,snapshot 名称为 “snap”。
查看 rdb:
root@ceph1:~# rbd info images/71dc76da-774c-411f-a958-1b51816ec50f
rbd image
'71dc76da-774c-411f-a958-1b51816ec50f':
size
40162 kB in 5 objects
order
23 (8192 kB objects)
block_name_prefix: rbd_data.103d2246be0e
format:
2
features: layering
Glance 自动创建了它的 snapshot:
root@ceph1:~# rbd snap ls images/71dc76da-774c-411f-a958-1b51816ec50f
SNAPID NAME SIZE
2 snap 40162 kB
查看该 snapshot:
root@ceph1:~# rbd info images/71dc76da-774c-411f-a958-1b51816ec50f@snap
rbd image '71dc76da-774c-411f-a958-1b51816ec50f':
size 40162 kB in 5 objects
order 23 (8192 kB objects)
block_name_prefix: rbd_data.103d2246be0e
format: 2
features: layering
protected: True

基于该 Glance image 创建的 Cinder volume 都是该 snapshot 的 clone:

root@ceph1:~# rbd children images/71dc76da-774c-411f-a958-1b51816ec50f@snap
volumes
/volume-65dbaf38-0b9d-4654-bba4-53f12cc906e3
volumes
/volume-6868a043-1412-4f6c-917f-bbffb1a8d21a

此时如果试着去删除该 image,则会报错:

The image cannot be deleted because it is in use through the backend store outside of Glance.
这是因为该 image 的 rbd image 的 snapshot 被使用了。

2. Cinder 与 Ceph RBD 的集成

OpenStack Cinder 组件和 Ceph RBD 集成的目的是将 Cinder 卷(volume)保存在 Ceph RBD 中。

2.1 配置项

配置项含义默认值
rbd_pool保存rbd 卷的ceph pool 名称rbd
rbd_usercucumber java从入门到精通(3)简单实现及断言 - 乙醇  阅读原文»

cucumber java从入门到精通(3)简单实现及断言

上一节里我们定义了step的java代码实现文件,step就是测试步骤及断言的集合,我们先定义出来,以后可以驱动开发以及在持续集成时重用。

这一节我们将近距离细观一下所谓的step java实现。以下面的代码片段为例:

public class TodoStep { //1
@假设("^我的任务清单里有(\\d+)个任务$") //2
public void iHaveSomeTasks(int totalTasks) throws Throwable { //3
// Write code here that turns the phrase above into concrete actions
throw new PendingException(); //4
}
}
  • //1 定义了public class,这没什么好说的;
  • //2 假设注解,这个注解表明下面的方法对应的也就是feature文件中我的任务清单里有xxxx个任务这个步骤;
  • //3 定义了具体实现feature文件步骤的方法,并从feature定义中取得传入参数,也就是xxxx个任务的具体值;
  • //4 抛出Pending异常,表明该步骤暂未实现,但来日方长,也许有天可以实现;

cucmber执行顺序

如果你对上面的代码尚有疑问,那么是时候看一下cucumber的执行顺序了,以下面代码片段为例:

# feature
假设 我的任务清单里有3个任务 // 1
当 我完成1件任务之后 // 2
那么 我还剩下2件未完成的任务 //3

# step
@假设("^我的任务清单里有(\\d+)个任务$") //4
public void iHaveSomeTasks(int totalTasks) throws Throwable { //5
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}

@当("^我完成(\\d+)件任务之后$") //6
public void iFinishSomeTasks(int finishedTasks) throws Throwable { //7
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}

@那么("^我还剩下(\\d+)件未完成的任务$") //8
public void iLeftSomeTasks(int leftTasks) throws Throwable { //9
// Write code here that turns the phrase above into concrete actions
throw new PendingException();
}
  • 当我们运行了run命令后(还记得上一节的run吗?其实就是执行了cucumber.api.cli.Main。),cucumber会去找feature文件,然后执行第1条feature语句,也就是//1
  • 执行//1的时候,cucumber会去找对应的step定义文件,寻寻觅觅的过程中,cucumber发现了对应的注解,也就是//4
  • //4告诉cucumber,下面紧接着定义的那个方法就是该feature对应的代码实现,于是cucumber再去执行//5
  • 在执行//5的时候,cucumber将3作为totalTasks这个参数传给//5
  • cucumber执行//5方法体中的内容并收到pending异常,于是该feature执行结束,转去执行下1条feature

小练习:你能自己说明//2以及//3的执行顺序吗

渐进重构之假装实现

回忆一下我们的测试目标,我们要测试一个todo list,第1步也就是在测试背景或者叫做前置条件里,我们是这样描述的

假设 我的任务清单里有3个任务 // 1

这时候不妨假设我们有个TodoList类,该类有个getTotalTaskCount()方法返回任务清单中一共有多少条任务。基于这个想法,我们重构一下TodoStep.java文件


@假设("^我的任务清单里有(\\d+)个任务$")
public void iHaveSomeTasks(int totalTasks) throws Throwable {
// totalTasks == 3
TodoList todo = new TodoList();
assertEquals(todo.getTotalTaskCount(), totalTasks);
}

在这里要说明的是注解中的(\d)表示获取feature定义中的数字字符并把该数字(int)作为totalTasks参数传入iHaveSomeTasks方法。因此totalTaks应该等于3。另外assertEquals是jUnit的断言方法

执行一下compile && run,得到下面的结果

step_definitions\TodoStep.java:11: 错误: 找不到符号
TodoList todo = new TodoList();
^
符号: 类 TodoList
位置: 类 TodoStep
step_definitions\TodoStep.java:11: 错误: 找不到符号
TodoList todo = new TodoList();
^
符号: 类 TodoList
位置: 类 TodoStep
2 个错误

我们无法编译,因为TodoList这个类并没有定义。

下面我们定义一下TodoList类。在implementation文件夹中新建1个名为TodoList.java的文件,该文件的实现是

// TodoList.java
package implementation;

public class TodoList {
public int getTotalTaskCount() {
return 3;
}
}

下面在TodoStep.java中导入TodoList类

// TodoStep.java
import implementation.TodoList;

修改一下compile文件,因为这次我们需要TodoList.java文件,修改完成的版本应该是这样的

# compile
javac -cp "./jars/*;." step_definitions\TodoStep.java implementation\TodoList.java
# linux && unix
javac -cp "./jars/*:." step_definitions\TodoStep.java implementation\TodoList.java

然后运行compile && run,得到下面的结果

#language: zh-CN
功能: 任务管理

场景: 完成1件任务 # todo.feature:5
假设我的任务清单里有3个任务 # TodoStep.iHaveSomeTasks(int)
当我完成1件任务之后 # TodoStep.iFinishSomeTasks(int)
cucumber.api.PendingException: TODO: implement me
at step_definitions.TodoStep.iFinishSomeTasks(TodoStep.java:19)
at ?.当我完成1件任务之后(todo.feature:7)

那么我还剩下2件未完成的任务 # TodoStep.iLeftSomeTasks(int)

1 Scenarios (1 pending)
3 Steps (1 skipped, 1 pending, 1 passed)
0m0.128s

cucumber.api.PendingException: TODO: implement me
at step_definitions.TodoStep.iFinishSomeTasks(TodoStep.java:19)
at ?.当我完成1件任务之后(todo.feature:7)

我们看到有1个step pass了,那就是我们刚定义前置条件step,所以到现在为止我们干的不错!我们有了feature文件,该文件描述了需求和测试用例,我们完成了测试step中的前置条件,并且我们实现了1个真正的TodoList类,尽管这个类目前还是嗷嗷待哺,不过当我们实现更多的step之后,TodoList类的功能会进一步完善,直到满足用户需求。实际上我们现在做的就是BDD,用测试去驱动开发。

可能有的同学会对此不屑一顾,TodoList类到目前为止只是自欺欺人的实现了1个返回int型数字3的方法,离我们所要的任务清单还相差十万八千里。其实不用担心这个,我们的TodoList类没有实现具体的功能是因为我们实现的测试步骤还不够多,我们接收到的需求还只是那么一点点,当我们实现更多步骤之后,TodoList自然会羽翼丰满。这就是所谓的最小实现原则。

渐进重构,重构剩下的2个步骤

下面我们重构剩下的2个步骤。

我们先假设TodoList有1个finishTask方法,每次调用这个方法就会完成n个任务,n从参数传入。于是剩余的任务就会减去n。
我们再假设TodoList有1个getRestTasksCount方法,调用这个方法可以获取剩下的task的数量。
重构完成以后,我们的代码如下所示

package step_definitions;

import cucumber.api.java.zh_cn.*;
import cucumber.api.PendingException;
import static org.junit.Assert.*;
import implementation.TodoList;

public class TodoStep {
TodoList todo;

@假设("^我的任务清单里有(\\d+)个任务$")
public void iHaveSomeTasks(int totalTasks) throws Throwable {
// Write code here that turns the phrase above into concrete actions
todo = new TodoList();
assertEquals(todo.getTotalTaskCount(), totalTasks);
}

@当("^我完成(\\d+)件任务之后$")
public void iFinishSomeTasks(int finishedTasks) throws Throwable {
// Write code here that turns the phrase above into concrete actions
todo.finisheTask(finishedTasks);
}

@那么("^我还剩下(\\d+)件未完成的任务$")
public void iLeftSomeTasks(int leftTasks) throws Throwable {
// Write code here that turns the phrase above into concrete actions
assertEquals(todo.getRestTasksCount(), leftTasks);
}
}

编译一下,不出意外的失败了,没关系,我们再去重构TodoList.java文件。

我们先实现finishedTask方法,这个方法的方法体是空,表示什么都不做;
然后实现getRestTasksCount方法,这个方法简单的返回2就好;

#TodoList.java
package implementation;

public class TodoList {
public int getTotalTaskCount() {
return 3;
}

public void finishTask(int count) {

}

public int getRestTasksCount() {
return 2;
}
}

再次运行阅读更多内容

没有评论:

发表评论