docker源码4-卷相关的方法调用

谢绝转载

序言

终于看完了volumeservice的初始化,铺垫了这么多,就是为了看一下卷相关方法的调用,那就快进入正题吧:

注意事项:
1.本文共有四篇,每篇都有编号,编号类似1.2.1这种,其中1是文章编号,因为后面的调用关系需要去前面篇幅中找,所以我标注了这个方便寻找.

2.我是按调用过程列出代码,如果当前函数有多个地方需要讲解,比如函数1.2中有两个地方需要讲解,那么要展开的地方便是1.2.1,1.2.2这样排列.

3.链接:
第一篇:https://www.jianshu.com/p/9900ec52f2c1 (命令的调用流程)
第二篇:https://www.jianshu.com/p/db08b7d57721 (卷服务初始化)
第三篇:https://www.jianshu.com/p/bbc73f5687a2 (plugin的管理)
第四篇:https://www.jianshu.com/p/a92b1b11c8dd (卷相关命令的执行)

卷相关命令的调用

4.1 initRouter函数

path func name line number
components/engine/cmd/dockerd/daemon.go initRouter 480
func initRouter(opts routerOptions) {
    decoder := runconfig.ContainerDecoder{}

    routers := []router.Router{
        ...
        volume.NewRouter(opts.daemon.VolumesService()),
        ...
    }
...
}

而volume.NewRouter方法的定义如下:

4.2 NewRouter函数

path func name line number
components/engine/api/server/router/volume/volume.go NewRouter 12
// NewRouter initializes a new volume router
func NewRouter(b Backend) router.Router {
    r := &volumeRouter{
        backend: b,
    }
    r.initRoutes()
    return r
}

继续看路由的初始化:

4.3 initRoutes方法

path func name line number
components/engine/api/server/router/volume/volume.go initRoutes 25
func (r *volumeRouter) initRoutes() {
    r.routes = []router.Route{
        // GET
        router.NewGetRoute("/volumes", r.getVolumesList),
        router.NewGetRoute("/volumes/{name:.*}", r.getVolumeByName),
        // POST
        router.NewPostRoute("/volumes/create", r.postVolumesCreate),
        router.NewPostRoute("/volumes/prune", r.postVolumesPrune, router.WithCancel),
        // DELETE
        router.NewDeleteRoute("/volumes/{name:.*}", r.deleteVolumes),
    }
}

而具体的方法是通过传入的volumeservice来实现的,以List方法为例,即getVolumesList实际执行的是传入的volumeservice的List方法:

4.4 getVolumesList方法

path func name line number
components/engine/api/server/router/volume/volume_routes.go getVolumesList 17
func (v *volumeRouter) getVolumesList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    ...
    volumes, warnings, err := v.backend.List(ctx, filters)
    ...
}

4.5 List方法

我们就按最常规的方式,不指定具体的filter,看一下这个方法如何执行.

path func name line number
components/engine/volume/service/service.go List 226
func (s *VolumesService) List(ctx context.Context, filter filters.Args) (volumesOut []*types.Volume, warnings []string, err error) {
    by, err := filtersToBy(filter, acceptedListFilters)
    if err != nil {
        return nil, nil, err
    }

    # 调用vs的find方法,vs是前面提到的VolumeStore
    volumes, warnings, err := s.vs.Find(ctx, by)
    if err != nil {
        return nil, nil, err
    }

    return s.volumesToAPI(ctx, volumes, useCachedPath(true)), warnings, nil
}

我们按filter为空的情况继续执行s.vs.Find方法,Find方法中调用到了前面提到的ds[],也就是存储driver的Store:

4.6 Find方法

path func name line number
components/engine/volume/service/store.go Find 321
// Find lists volumes filtered by the past in filter.
// If a driver returns a volume that has name which conflicts with another volume from a different driver,
// the first volume is chosen and the conflicting volume is dropped.
func (s *VolumeStore) Find(ctx context.Context, by By) (vols []volume.Volume, warnings []string, err error) {
    logrus.WithField("ByType", fmt.Sprintf("%T", by)).WithField("ByValue", fmt.Sprintf("%+v", by)).Debug("VolumeStore.Find")
    switch f := by.(type) {
    case nil, orCombinator, andCombinator, byDriver, ByReferenced, CustomFilter:
        # 在filter中加载卷
        warnings, err = s.filter(ctx, &vols, by)
    ...
    }
    if err != nil {
        return nil, nil, &OpErr{Err: err, Op: "list"}
    }

    var out []volume.Volume

    for _, v := range vols {
        name := normalizeVolumeName(v.Name())

        s.locks.Lock(name)
        # 从内存中加载卷的名称
        storedV, exists := s.getNamed(name)
        // Note: it's not safe to populate the cache here because the volume may have been
        // deleted before we acquire a lock on its name
        # 如果存在并且driver不同,则保留第一个同名称的卷
        if exists && storedV.DriverName() != v.DriverName() {
            logrus.Warnf("Volume name %s already exists for driver %s, not including volume returned by %s", v.Name(), storedV.DriverName(), v.DriverName())
            s.locks.Unlock(v.Name())
            continue
        }

        out = append(out, v)
        s.locks.Unlock(v.Name())
    }
    return out, warnings, nil
}

上面的方法先加载卷,然后再跟缓存中的进行对比,如果名称相同而driver不同,则跳过第二个同名称的卷. 加载卷的过程如下:

4.7 filter方法

path func name line number
components/engine/volume/service/store.go filter 222
func (s *VolumeStore) filter(ctx context.Context, vols *[]volume.Volume, by By) (warnings []string, err error) {
    // note that this specifically does not support the `FromList` By type.
    switch f := by.(type) {
    case nil:
        if *vols == nil {
            var ls []volume.Volume
            ls, warnings, err = s.list(ctx)
            if err != nil {
                return warnings, err
            }
            *vols = ls
        }
    ...
    }
    return warnings, nil
}

继续看s.list方法,这个方法就会调用到我们在自定义的卷plugin中实现的接口方法:

4.8 list方法

path func name line number
components/engine/volume/service/store.go list 376
// list goes through each volume driver and asks for its list of volumes.
// TODO(@cpuguy83): plumb context through
func (s *VolumeStore) list(ctx context.Context, driverNames ...string) ([]volume.Volume, []string, error) {
    var (
        ls       = []volume.Volume{} // do not return a nil value as this affects filtering
        warnings []string
    )

    # volume.Driver是个接口
    var dls []volume.Driver
    # GetAllDrivers加载所有的VolumeDriver,返回值其实是一个driver的适配器
    # s.drivers也就是之前讲到的ds[2.3.2.2 章节]
    # 此处是重点
    all, err := s.drivers.GetAllDrivers()
    ...

    type vols struct {
        vols       []volume.Volume
        err        error
        driverName string
    }
    chVols := make(chan vols, len(dls))

    for _, vd := range dls {
        go func(d volume.Driver) {
            # 调用plugin中实现的List方法获取卷
            # 此处是重点
            vs, err := d.List()
            if err != nil {
                chVols <- vols{driverName: d.Name(), err: &OpErr{Err: err, Name: d.Name(), Op: "list"}}
                return
            }
            for i, v := range vs {
                s.globalLock.RLock()
                vs[i] = volumeWrapper{v, s.labels[v.Name()], d.Scope(), s.options[v.Name()]}
                s.globalLock.RUnlock()
            }

            chVols <- vols{vols: vs}
        }(vd)
    }

    ...
    return ls, warnings, nil
}

上面的s.drivers.GetAllDrivers方法加载所有注册过的drivers:

4.8.1 GetAllDrivers方法

path func name line number
components/engine/volume/drivers/extpoint.go GetAllDrivers 177
// GetAllDrivers lists all the registered drivers
func (s *Store) GetAllDrivers() ([]volume.Driver, error) {
    var plugins []getter.CompatPlugin
    if s.pluginGetter != nil {
        var err error
        # 这里的store就是前面讲过的ds[2.3.2.3章节], ds的pluginGetter就是前面讲到的d.pluginStore
        # 过滤卷driver的名称 extName = "VolumeDriver"
        plugins, err = s.pluginGetter.GetAllByCap(extName)
        if err != nil {
            return nil, fmt.Errorf("error listing plugins: %v", err)
        }
    }
    # 此处的volume.Driver是个接口
    var ds []volume.Driver

    s.mu.Lock()
    defer s.mu.Unlock()

    #先加缓存过的drivers
    for _, d := range s.extensions {
        ds = append(ds, d)
    }
    #加载没有缓存过的driver
    for _, p := range plugins {
        name := p.Name()

        if _, ok := s.extensions[name]; ok {
            continue
        }

        # 用plugin创建对应的driver适配器,适配器中的方法调用实际driver的方法
        # 此处是重点
        ext, err := makePluginAdapter(p)
        if err != nil {
            return nil, errors.Wrap(err, "error making plugin client")
        }
        if p.IsV1() {
            s.extensions[name] = ext
        }
        ds = append(ds, ext)
    }
    return ds, nil
}
  • 4.8.1.1 Driver接口

先看volume.Driver接口:

path interface name line number
components/engine/volume/volume.go Driver 19
// Driver is for creating and removing volumes.
type Driver interface {
    // Name returns the name of the volume driver.
    Name() string
    // Create makes a new volume with the given name.
    Create(name string, opts map[string]string) (Volume, error)
    // Remove deletes the volume.
    Remove(vol Volume) (err error)
    // List lists all the volumes the driver has
    List() ([]Volume, error)
    // Get retrieves the volume with the requested name
    Get(name string) (Volume, error)
    // Scope returns the scope of the driver (e.g. `global` or `local`).
    // Scope determines how the driver is handled at a cluster level
    Scope() string
}

然后继续看makePluginAdapter方法

  • 4.8.1.2 makePluginAdapter方法

可知返回值是volumeDriverAdapter,而volumeDriverAdapter结构体实现了volume.Driver接口:

path func name line number
components/engine/volume/drivers/extpoint.go makePluginAdapter 214
func makePluginAdapter(p getter.CompatPlugin) (*volumeDriverAdapter, error) {
    # 如果是v1/http plugin client方式的plugin
    if pc, ok := p.(getter.PluginWithV1Client); ok {
        return &volumeDriverAdapter{name: p.Name(), scopePath: p.ScopedPath, proxy: &volumeDriverProxy{pc.Client()}}, nil
    }

    # 如果是PluginAddr方式的plugin
    pa, ok := p.(getter.PluginAddr)
    ...

    addr := pa.Addr()
    # 此处的client与上面的pc.Client()都是同一个类型,即*plugins.client
    client, err := plugins.NewClientWithTimeout(addr.Network()+"://"+addr.String(), nil, pa.Timeout())
    if err != nil {
        return nil, errors.Wrap(err, "error creating plugin client")
    }

    return &volumeDriverAdapter{name: p.Name(), scopePath: p.ScopedPath, proxy: &volumeDriverProxy{client}}, nil    ...
}

不管是哪种方式的plugin,都要构造一个volumeDriverAdapter,其成员变量proxy作为plugin的客户端.

  • 4.8.1.3 volumeDriverAdapter结构体

volumeDriverAdapter结构体定义如下:

path struct name line number
components/engine/volume/drivers/adapter.go volumeDriverAdapter 16
type volumeDriverAdapter struct {
    name         string
    scopePath    func(s string) string
    capabilities *volume.Capability
    proxy        volumeDriver
}

其中,proxy是volumeDriver接口类型

  • 4.8.1.4 volumeDriver接口
path interface name line number
components/engine/volume/drivers/extpoint.go volumeDriver 25
type volumeDriver interface {
    // Create a volume with the given name
    Create(name string, opts map[string]string) (err error)
    // Remove the volume with the given name
    Remove(name string) (err error)
    // Get the mountpoint of the given volume
    Path(name string) (mountpoint string, err error)
    // Mount the given volume and return the mountpoint
    Mount(name, id string) (mountpoint string, err error)
    // Unmount the given volume
    Unmount(name, id string) (err error)
    // List lists all the volumes known to the driver
    List() (volumes []*proxyVolume, err error)
    // Get retrieves the volume with the requested name
    Get(name string) (volume *proxyVolume, err error)
    // Capabilities gets the list of capabilities of the driver
    Capabilities() (capabilities volume.Capability, err error)
}
  • 4.8.1.5 volumeDriverProxy结构体

新建volumeDriverAdapter的时候用volumeDriverProxy给成员变量proxy赋值,所以volumeDriverProxy中要实现volumeDriver的各种接口.

看下volumeDriverProxy的定义:

path struct name line number
components/engine/volume/drivers/proxy.go volumeDriverProxy 22
type volumeDriverProxy struct {
    client
}

匿名成员client是一个接口:

  • 4.8.1.6 client接口
path interface name line number
components/engine/volume/drivers/proxy.go client 18
type client interface {
    CallWithOptions(string, interface{}, interface{}, ...func(*plugins.RequestOpts)) error
}
  • 4.8.1.7 CallWithOptions方法
    这个接口的实现即是2.2.5.3中的Client结构体,再看Client结构体的CallWithOptions方法的定义:
path func name line number
components/engine/pkg/plugins/client.go CallWithOptions 106
// CallWithOptions is just like call except it takes options
func (c *Client) CallWithOptions(serviceMethod string, args interface{}, ret interface{}, opts ...func(*RequestOpts)) error {
    var buf bytes.Buffer
    if args != nil {
        if err := json.NewEncoder(&buf).Encode(args); err != nil {
            return err
        }
    }
    body, err := c.callWithRetry(serviceMethod, &buf, true, opts...)
    if err != nil {
        return err
    }
    defer body.Close()
    if ret != nil {
        if err := json.NewDecoder(body).Decode(&ret); err != nil {
            logrus.Errorf("%s: error reading plugin resp: %v", serviceMethod, err)
            return err
        }
    }
    return nil
}

主要作用就是根据传入的serviceMethod调用对应的plugin中的方法.

4.8.2 List 方法

再回到4.8章节,去看d.List()方法调用,实际上就是volumeDriverAdapter的List方法

path func name line number
components/engine/volume/drivers/adapter.go List 43
func (a *volumeDriverAdapter) List() ([]volume.Volume, error) {
    ls, err := a.proxy.List()
    if err != nil {
        return nil, err
    }

    var out []volume.Volume
    for _, vp := range ls {
        out = append(out, &volumeAdapter{
            proxy:      a.proxy,
            name:       vp.Name,
            scopePath:  a.scopePath,
            driverName: a.name,
            eMount:     a.scopePath(vp.Mountpoint),
        })
    }
    return out, nil
}

a.proxy.List()就调用到具体的你实现的plugin的List方法了.

  • 4.8.2.1 List方法
path func name line number
components/engine/volume/drivers/proxy.go List 181
func (pp *volumeDriverProxy) List() (volumes []*proxyVolume, err error) {
    var (
        req volumeDriverProxyListRequest
        ret volumeDriverProxyListResponse
    )

    if err = pp.CallWithOptions("VolumeDriver.List", req, &ret, plugins.WithRequestTimeout(shortTimeout)); err != nil {
        return
    }

    volumes = ret.Volumes

    if ret.Err != "" {
        err = errors.New(ret.Err)
    }

    return
}

如果你使用过go-plugins-helpers的话,你应该熟悉下面的Handle方法:

path
volume/api.go
const (
    // DefaultDockerRootDirectory is the default directory where volumes will be created.
    DefaultDockerRootDirectory = "/var/lib/docker-volumes"

    manifest         = `{"Implements": ["VolumeDriver"]}`
    listPath         = "/VolumeDriver.List"
    ...
)

func (h *Handler) initMux() {
    ...
    h.HandleFunc(listPath, func(w http.ResponseWriter, r *http.Request) {
        log.Println("Entering go-plugins-helpers listPath")
        res, err := h.driver.List()
        if err != nil {
            sdk.EncodeResponse(w, NewErrorResponse(err.Error()), true)
            return
        }
        sdk.EncodeResponse(w, res, false)
    })
    ...
}

总结

以上就是volumeService的初始化以及加载对应docker volume plugin的过程,要弄懂的话根据这个流程看一遍,基本就明白了.所有的东西都是为了把源码中卷相关的操作和我们自己实现的go-plugins-helpers中的接口方法连接起来.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容

  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 3,844评论 0 5
  • Build, Ship, and Run Any App, Anywhere 可见容器技术的使用给我们带来了效率上...
    猴子精h阅读 1,945评论 0 0
  • 我说带孩子回老家,老妈一口答应。本想着回家有妈妈帮忙带娃,我可以轻松一下了,所以带了几本书回家,结果事情根本不是我...
    露露Yao阅读 183评论 1 1
  • 假如,明天的我不会再醒来,你会不会担心,惆怅,焦急,寻找
    唯杰不爱阅读 97评论 0 0
  • 这个周六,正值“雨水”节气,却无雨,天气晴暖。 闲步于秦淮河畔,沐浴在明媚的春光中,怡然情舒,抬头看看蓝蓝的天,...
    相逢萍水阅读 369评论 0 4