分享

存在竞态的openstack.live_snapshot实现方法

hochikong 发表于 2014-8-1 21:48:42 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 0 7867
问题导读:
1.实现live snapshot的过程是怎样的?
2.出现竞争场景该怎么解决?




openstack在H版中提供了live_snapshot,也就是不影响虚机业务运行的snapshot,代码中live_snapshot实现方法:
  1. def _live_snapshot()
  2.         try:
  3.             # NOTE (rmk): blockRebase cannot be executed on persistent
  4.             # domains, so we need to temporarily undefine it.
  5.             # If any part of this block fails, the domain is
  6.             # re-defined regardless.
  7.             if domain.isPersistent():
  8.                 domain.undefine()
  9.             # NOTE (rmk): Establish a temporary mirror of our root disk and
  10.             # issue an abort once we have a complete copy.
  11.             domain.blockRebase(disk_path, disk_delta, 0,
  12.                                libvirt.VIR_DOMAIN_BLOCK_REBASE_COPY |
  13.                                libvirt.VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT |
  14.                                libvirt.VIR_DOMAIN_BLOCK_REBASE_SHALLOW)
  15.             while self._wait_for_block_job(domain, disk_path):
  16.                 time.sleep(0.5)
  17.             domain.blockJobAbort(disk_path, 0)
  18.             libvirt_utils.chown(disk_delta, os.getuid())
  19.         finally:
  20.             self._conn.defineXML(xml)
  21. def _wait_for_block_job(domain, disk_path, abort_on_error=False):
  22.         status = domain.blockJobInfo(disk_path, 0)
  23.         if status == -1 and abort_on_error:
  24.             msg = _('libvirt error while requesting blockjob info.')
  25.             raise exception.NovaException(msg)
  26.         try:
  27.             cur = status.get('cur', 0)
  28.             end = status.get('end', 0)
  29.         except Exception:
  30.             return False
  31.         if cur == end and cur != 0 and end != 0:
  32.             return False
  33.         else:
  34.             return True
复制代码
过程分析
*openstack层面: 首先调用libvirt接口domain.blockRebase发起qemu对于磁盘的“mirror job",然后反复调用libvirt接口domain.blockJobInfo反复查询备份job,当current刻度与offset对齐时,调用domain.blockJobAbort结束job

*libvirt层面: domain.blockRebase调用qemu接口drive_mirror,domain.blockJobInfo调用qemu接口info blockjob,domain.blockJobInfo是一个同步接口,先调用qemu blockjob-cancel停止任务,然后不断查询,直到任务被关闭才返回

*qemu层面:mirror任务的注释”Start mirroring a block device's writes to a new destination,using the specified target.“,其中重要循环:
block/mirror.c


  1. static void coroutine_fn mirror_run(void *opaque)
  2.     for (;;) {
  3.         uint64_t delay_ns;
  4.         int64_t cnt;
  5.         bool should_complete;
  6.         if (s->ret < 0) {
  7.             ret = s->ret;
  8.             goto immediate_exit;
  9.         }
  10.         cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
  11.         /* Note that even when no rate limit is applied we need to yield
  12.          * periodically with no pending I/O so that qemu_aio_flush() returns.
  13.          * We do so every SLICE_TIME nanoseconds, or when there is an error,
  14.          * or when the source is clean, whichever comes first.
  15.          */
  16.         if (qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - last_pause_ns < SLICE_TIME &&
  17.             s->common.iostatus == BLOCK_DEVICE_IO_STATUS_OK) {
  18.             if (s->in_flight == MAX_IN_FLIGHT || s->buf_free_count == 0 ||
  19.                 (cnt == 0 && s->in_flight > 0)) {
  20.                 trace_mirror_yield(s, s->in_flight, s->buf_free_count, cnt);
  21.                 qemu_coroutine_yield();
  22.                 continue;
  23.             } else if (cnt != 0) {
  24.                 mirror_iteration(s);
  25.                 continue;
  26.             }
  27.         }
  28.         should_complete = false;
  29.         if (s->in_flight == 0 && cnt == 0) {
  30.             trace_mirror_before_flush(s);
  31.             ret = bdrv_flush(s->target);
  32.             if (ret < 0) {
  33.                 if (mirror_error_action(s, false, -ret) == BDRV_ACTION_REPORT) {
  34.                     goto immediate_exit;
  35.                 }
  36.             } else {
  37.                 /* We're out of the streaming phase. From now on, if the job
  38.                  * is cancelled we will actually complete all pending I/O and
  39.                  * report completion. This way, block-job-cancel will leave
  40.                  * the target in a consistent state.
  41.                  */
  42.                 s->common.offset = end * BDRV_SECTOR_SIZE;
  43.                 if (!s->synced) {
  44.                     block_job_ready(&s->common);
  45.                     s->synced = true;
  46.                 }
  47.                 should_complete = s->should_complete ||
  48.                     block_job_is_cancelled(&s->common);
  49.                 cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
  50.             }
  51.         }
  52.         if (cnt == 0 && should_complete) {
  53.             /* The dirty bitmap is not updated while operations are pending.
  54.              * If we're about to exit, wait for pending operations before
  55.              * calling bdrv_get_dirty_count(bs), or we may exit while the
  56.              * source has dirty data to
  57.              *
  58.              * Note that I/O can be submitted by the guest while
  59.              * mirror_populate runs.
  60.              */
  61.             trace_mirror_before_drain(s, cnt);
  62.             bdrv_drain_all();
  63.             cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
  64.         }
  65.         ret = 0;
  66.         trace_mirror_before_sleep(s, cnt, s->synced);
  67.         if (!s->synced) {
  68.             /* Publish progress */
  69.             s->common.offset = (end - cnt) * BDRV_SECTOR_SIZE;
  70.             if (s->common.speed) {
  71.                 delay_ns = ratelimit_calculate_delay(&s->limit, sectors_per_chunk);
  72.             } else {
  73.                 delay_ns = 0;
  74.             }
  75.             block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, delay_ns);
  76.             if (block_job_is_cancelled(&s->common)) {
  77.                 break;
  78.             }
  79.         } else if (!should_complete) {
  80.             delay_ns = (s->in_flight == 0 && cnt == 0 ? SLICE_TIME : 0);
  81.             block_job_sleep_ns(&s->common, QEMU_CLOCK_REALTIME, delay_ns);
  82.         } else if (cnt == 0) {
  83.             /* The two disks are in sync. Exit and report successful
  84.              * completion.
  85.              */
  86.             assert(QLIST_EMPTY(&bs->tracked_requests));
  87.             s->common.cancelled = false;
  88.             break;
  89.         }
  90.         last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
  91.     }
复制代码
同步任务不断循环检查脏数据,有两种退出可能:1.source和target未同步时就设置了job->canceled 2.source和target   2.source和target同步后,should_complete && 迭代中脏页计数为0,而should_complete成立的条件是脏页为0的迭代并且job设置了退出;所以在这个设备不断IO的情况下,只有一个很小 的空当可以通过设置job状态而退出,而上层的openstack通过sleep(0.5)来钻这个空当,呵呵。

带来的后果
1.同步任务会一直进行下去,直到GUEST OS中IO停止,造成宿主机资源一直被占用
2.libvirt的.blockJobAbort接口一直不返回,如果nova调用libvirt设置的阻塞方式,则nova也会被卡主

复现问题的方法
将sleep(x)的值调大后非常容易复现这个竞争场景,默认的0,5也有出现的机会

解决方法
1.在qemu中加入强制退出job的流程。
2.慎用mirror接口,采用其他方法在线备份。


#####################################################################
本文转自:http://blog.chinaunix.net/uid-29718549-id-4346700.html



欢迎加入about云群9037177932227315139327136 ,云计算爱好者群,亦可关注about云腾讯认证空间||关注本站微信

没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条