runsisi's

technical notes

Cinder 云盘创建与挂载流程

2019-07-08 runsisiopenstack

Cinder 的设计遵循 service -> manager -> driver 的三层架构。

主要包括 cinder-api, cinder-scheduler, cinder-volume 三大核心组件(进程),组件之间通过 RPC 进行通信。

RESTful 入口 cinder-api

# etc/nova/api-paste.ini

[app:apiv3]
paste.app_factory = cinder.api.v3.router:APIRouter.factory
# cinder/api/openstack/__init__py

class APIRouter(base_wsgi.Router):
  def __init__
    ext_mgr = self.ExtensionManager()
    self._setup_routes(mapper, ext_mgr)
    self._setup_ext_routes(mapper, ext_mgr)
    self._setup_extensions(ext_mgr)
# cinder/api/v3/router.py

class APIRouter(cinder.api.openstack.APIRouter):
  ExtensionManager = extensions.ExtensionManager
  
  def _setup_routes(self, mapper, ext_mgr):
    self.resources['volumes'] = volumes.create_resource(ext_mgr)
      mapper.resource("volume", "volumes",
                      controller=self.resources['volumes'],
                      collection={'detail': 'GET', 'summary': 'GET'},
                      member={'action': 'POST'})
    self.resources['attachments'] = attachments.create_resource(ext_mgr)
      mapper.resource("attachment", "attachments",
                      controller=self.resources['attachments'],
                      collection={'detail': 'GET', 'summary': 'GET'},
                      member={'action': 'POST'})

云盘创建

# cinder/api/v3/volumes.py

class VolumeController(volumes_v2.VolumeController):
  def create
    self.volume_api.create
# cinder/volume/api.py

class API(base.Base):
  def create
    flow_engine = create_volume.get_flow
    flow_engine.run()
# cinder/volume/flow/api/create_volume.py

def get_flow
  api_flow.add(VolumeCastTask(scheduler_rpcapi, volume_rpcapi, db_api))

class VolumeCastTask(flow_utils.CinderTask):
  def execute
    self._cast_create_volume

  def _cast_create_volume
    self.scheduler_rpcapi.create_volume

Cinder 的 scheduler 没有像 Nova 一样提供 api 对 rpcapi 进行封装,整个组件间的交互流程也比 Nova 要简单清晰的很多

# cinder/scheduler/rpcapi.py

class SchedulerAPI(rpc.RPCAPI):
  def create_volume
    cctxt.cast(ctxt, 'create_volume', **msg_args)
# cinder/scheduler/manager.py

scheduler_manager_opts = [
  cfg.StrOpt('scheduler_driver',
             default='cinder.scheduler.filter_scheduler.FilterScheduler',
             help='Default scheduler driver to use'),
]

class SchedulerManager(manager.CleanableManager, manager.Manager):
  def create_volume
    flow_engine = create_volume.get_flow
      scheduler_flow.add(ScheduleCreateVolumeTask(driver_api))
    flow_engine.run()
# cinder/scheduler/flows/create_volume.py

def get_flow
  scheduler_flow.add(ScheduleCreateVolumeTask(driver_api))

class ScheduleCreateVolumeTask(flow_utils.CinderTask):
  def execute
    # 驱动由配置选项 scheduler_driver 决定,默认值为 cinder.scheduler.filter_scheduler.FilterScheduler
    self.driver_api.schedule_create_volume
# cinder/scheduler/filter_scheduler.py

class FilterScheduler(driver.Scheduler):
  def schedule_create_volume
    self._schedule
      self._get_weighted_candidates
      self._choose_top_backend
    self.volume_rpcapi.create_volume

从 cinder-api 到 cinder-scheduler,现在终于到了 cinder-volume,需要注意的是,每个不同的后端,会创建一个对应的 manager,其中 class VolumeManagerservice_name 初始化参数决定了 manager 对应的后端卷驱动(请参考 cinder/cmd/volume.py 对 CONF.enabled_backends 选项的处理)

# cinder/volume/rpcapi.py

class VolumeAPI(rpc.RPCAPI):
  def create_volume
    cctxt.cast(ctxt, 'create_volume',
               request_spec=request_spec,
               filter_properties=filter_properties,
               allow_reschedule=allow_reschedule,
               volume=volume)
# cinder/volume/manager.py

class VolumeManager(manager.CleanableManager,
                    manager.SchedulerDependentManager):
  def create_volume
    flow_engine = create_volume.get_flow
    flow_engine.run()
# cinder/volume/flows/manager/create_volume.py

def get_flow
  volume_flow.add(ExtractVolumeSpecTask(db),
                  NotifyVolumeActionTask(db, "create.start"),
                  CreateVolumeFromSpecTask(manager,
                                           db,
                                           driver,
                                           image_volume_cache),
                  CreateVolumeOnFinishTask(db, "create.end"))

class CreateVolumeFromSpecTask(flow_utils.CinderTask):
  def execute
    if create_type == 'raw':
      self._create_raw_volume
        self.driver.create_volume
    elif create_type == 'snap':
      self._create_from_snapshot
        self.driver.create_volume_from_snapshot
    elif create_type == 'source_vol':
      self._create_from_source_volume
        self.driver.create_cloned_volume
    elif create_type == 'image':
      elf._create_from_image
    elif create_type == 'backup':
      self._create_from_backup

根据不同的后端类型,不同的驱动有不同的处理逻辑

# cinder/volume/drivers/rbd.py

class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
                driver.ManageableVD, driver.ManageableSnapshotsVD,
                driver.BaseVD):
  def create_volume
    self.RBDProxy().create

云盘挂载

Nova 进行云盘挂载时会调用 Cinder 的 attachments RESTful 接口返回 os_brick 挂载所需要的信息。

# cinder/api/v3/attachments.py

class AttachmentsController(wsgi.Controller):
  def update
    self.volume_api.attachment_update
# cinder/volume/api.py

class API(base.Base):
  def attachment_update
    self.volume_rpcapi.attachment_update
# cinder/volume/rpcapi.py

class VolumeAPI(rpc.RPCAPI):
  def attachment_update
    cctxt.call(ctxt,
               'attachment_update',
               vref=vref,
               connector=connector,
               attachment_id=attachment_id)
# cinder/volume/manager.py

class VolumeManager(manager.CleanableManager,
                    manager.SchedulerDependentManager):
  def attachment_update
    self._connection_create
      self.driver.initialize_connection
    # 回调,多数驱动都没有实现该接口,默认实现为空
    self.driver.attach_volume
# cinder/volume/drivers/rbd.py

def initialize_connection(self, volume, connector):
  hosts, ports = self._get_mon_addrs()
  data = {
    'driver_volume_type': 'rbd',
    'data': {
      'name': '%s/%s' % (self.configuration.rbd_pool,
                         volume.name),
      'hosts': hosts,
      'ports': ports,
      'cluster_name': self.configuration.rbd_cluster_name,
      'auth_enabled': (self.configuration.rbd_user is not None),
      'auth_username': self.configuration.rbd_user,
      'secret_type': 'ceph',
      'secret_uuid': self.configuration.rbd_secret_uuid,
      'volume_id': volume.id,
      "discard": True,
      'keyring': self._get_keyring_contents(),
    }
  }
  LOG.debug('connection data: %s', data)
  return data