github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/stage1/init/kvm/mount.go (about) 1 // Copyright 2015 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // mount.go provides functions for creating mount units for managing 16 // inner(kind=empty) and external(kind=host) volumes. 17 // note: used only for kvm flavor (lkvm based) 18 // 19 // Idea. 20 // For example when we have two volumes: 21 // 1) --volume=hostdata,kind=host,source=/host/some_data_to_share 22 // 2) --volume=temporary,kind=empty 23 // then in stage1/rootfs rkt creates two folders (in rootfs of guest) 24 // 1) /mnt/hostdata - which is mounted through 9p host thanks to 25 // lkvm --9p=/host/some_data_to_share,hostdata flag shared to quest 26 // 2) /mnt/temporary - is created as empty directory in guest 27 // 28 // both of them are then bind mounted to /opt/stage2/<application/<mountPoint.path> 29 // for every application, that has mountPoints specified in ACI json 30 // - host mounting is realized by podToSystemdHostMountUnits (for whole pod), 31 // which creates mount.units (9p) required and ordered before all applications 32 // service units 33 // - bind mounting is realized by appToSystemdMountUnits (for each app), 34 // which creates mount.units (bind) required and ordered before particular application 35 // note: systemd mount units require /usr/bin/mount 36 package kvm 37 38 import ( 39 "fmt" 40 "io/ioutil" 41 "log" 42 "os" 43 "path/filepath" 44 "strings" 45 46 "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/spec/schema" 47 "github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/spec/schema/types" 48 "github.com/coreos/rkt/Godeps/_workspace/src/github.com/coreos/go-systemd/unit" 49 "github.com/coreos/rkt/common" 50 initcommon "github.com/coreos/rkt/stage1/init/common" 51 ) 52 53 const ( 54 // location within stage1 rootfs where shared volumes will be put 55 // (or empty directories for kind=empty) 56 stage1MntDir = "/mnt/" 57 ) 58 59 // serviceUnitName returns a systemd service unit name for the given app name. 60 // note: it was shamefully copy-pasted from stage1/init/path.go 61 // TODO: extract common functions from path.go 62 func serviceUnitName(appName types.ACName) string { 63 return appName.String() + ".service" 64 } 65 66 // installNewMountUnit creates and installs a new mount unit in the default 67 // systemd location (/usr/lib/systemd/system) inside the pod stage1 filesystem. 68 // root is pod's absolute stage1 path (from Pod.Root). 69 // beforeAndrequiredBy creates a systemd unit dependency (can be space separated 70 // for multi). 71 // It returns the name of the generated unit. 72 func installNewMountUnit(root, what, where, fsType, options, beforeAndrequiredBy, unitsDir string) (string, error) { 73 opts := []*unit.UnitOption{ 74 unit.NewUnitOption("Unit", "Description", fmt.Sprintf("Mount unit for %s", where)), 75 unit.NewUnitOption("Unit", "DefaultDependencies", "false"), 76 unit.NewUnitOption("Unit", "Before", beforeAndrequiredBy), 77 unit.NewUnitOption("Mount", "What", what), 78 unit.NewUnitOption("Mount", "Where", where), 79 unit.NewUnitOption("Mount", "Type", fsType), 80 unit.NewUnitOption("Mount", "Options", options), 81 unit.NewUnitOption("Install", "RequiredBy", beforeAndrequiredBy), 82 } 83 84 unitsPath := filepath.Join(root, unitsDir) 85 unitName := unit.UnitNamePathEscape(where + ".mount") 86 87 if err := writeUnit(opts, filepath.Join(unitsPath, unitName)); err != nil { 88 return "", err 89 } 90 log.Printf("mount unit created: %q in %q (what=%q, where=%q)", unitName, unitsPath, what, where) 91 92 return unitName, nil 93 } 94 95 func writeUnit(opts []*unit.UnitOption, unitPath string) error { 96 unitBytes, err := ioutil.ReadAll(unit.Serialize(opts)) 97 if err != nil { 98 return fmt.Errorf("failed to serialize mount unit file to bytes %q: %v", unitPath, err) 99 } 100 101 err = ioutil.WriteFile(unitPath, unitBytes, 0644) 102 if err != nil { 103 return fmt.Errorf("failed to create mount unit file %q: %v", unitPath, err) 104 } 105 106 return nil 107 } 108 109 // PodToSystemdHostMountUnits create host shared remote file system 110 // mounts (using e.g. 9p) according to https://www.kernel.org/doc/Documentation/filesystems/9p.txt. 111 // Additionally it creates required directories in stage1MntDir and then prepares 112 // bind mount unit for each app. 113 // "root" parameter is stage1 root filesystem path. 114 // appNames are used to create before/required dependency between mount unit and 115 // app service units. 116 func PodToSystemdHostMountUnits(root string, volumes []types.Volume, appNames []types.ACName, unitsDir string) error { 117 // pod volumes need to mount p9 qemu mount_tags 118 for _, vol := range volumes { 119 // only host shared volumes 120 121 name := vol.Name.String() // acts as a mount tag 9p 122 // /var/lib/.../pod/run/rootfs/mnt/{volumeName} 123 mountPoint := filepath.Join(root, stage1MntDir, name) 124 125 // for kind "empty" that will be shared among applications 126 log.Printf("creating an empty volume folder for sharing: %q", mountPoint) 127 err := os.MkdirAll(mountPoint, 0700) 128 if err != nil { 129 return err 130 } 131 132 // serviceNames for ordering and requirements dependency for apps 133 var serviceNames []string 134 for _, appName := range appNames { 135 serviceNames = append(serviceNames, serviceUnitName(appName)) 136 } 137 138 // for host kind we create a mount unit to mount host shared folder 139 if vol.Kind == "host" { 140 _, err = installNewMountUnit(root, 141 name, // what (source) in 9p it is a channel tag which equals to volume.Name/mountPoint.name 142 filepath.Join(stage1MntDir, name), // where - destination 143 "9p", // fsType 144 "trans=virtio", // 9p specific options 145 strings.Join(serviceNames, " "), // space separated list of services for unit dependency 146 unitsDir, 147 ) 148 if err != nil { 149 return err 150 } 151 } 152 } 153 154 return nil 155 } 156 157 // AppToSystemdMountUnits prepare bind mount unit for empty or host kind mounting 158 // between stage1 rootfs and chrooted filesystem for application 159 func AppToSystemdMountUnits(root string, appName types.ACName, volumes []types.Volume, ra *schema.RuntimeApp, unitsDir string) error { 160 app := ra.App 161 162 vols := make(map[types.ACName]types.Volume) 163 for _, v := range volumes { 164 vols[v.Name] = v 165 } 166 167 mounts, err := initcommon.GenerateMounts(ra, vols) 168 if err != nil { 169 return err 170 } 171 172 for _, m := range mounts { 173 vol := vols[m.Volume] 174 175 // source relative to stage1 rootfs to relative pod root 176 whatPath := filepath.Join(stage1MntDir, vol.Name.String()) 177 whatFullPath := filepath.Join(root, whatPath) 178 179 // destination relative to stage1 rootfs and relative to pod root 180 wherePath := filepath.Join(common.RelAppRootfsPath(appName), m.Path) 181 whereFullPath := filepath.Join(root, wherePath) 182 183 // assertion to make sure that "what" exists (created earlier by PodToSystemdHostMountUnits) 184 log.Printf("checking required source path: %q", whatFullPath) 185 if _, err := os.Stat(whatFullPath); os.IsNotExist(err) { 186 return fmt.Errorf("bug: missing source for volume %v", vol.Name) 187 } 188 189 // optionally prepare app directory 190 log.Printf("optionally preparing destination path: %q", whereFullPath) 191 err := os.MkdirAll(whereFullPath, 0700) 192 if err != nil { 193 return fmt.Errorf("failed to prepare dir for mount %v: %v", m.Volume, err) 194 } 195 196 // install new mount unit for bind mount /mnt/volumeName -> /opt/stage2/{app-id}/rootfs/{{mountPoint.Path}} 197 mu, err := installNewMountUnit( 198 root, // where put a mount unit 199 whatPath, // what - stage1 rootfs /mnt/VolumeName 200 wherePath, // where - inside chroot app filesystem 201 "bind", // fstype 202 "bind", // options 203 serviceUnitName(appName), 204 unitsDir, 205 ) 206 if err != nil { 207 return fmt.Errorf("cannot install new mount unit for app %q: %v", appName.String(), err) 208 } 209 210 // TODO(iaguis) when we update util-linux to 2.27, this code can go 211 // away and we can bind-mount RO with one unit file. 212 // http://ftp.kernel.org/pub/linux/utils/util-linux/v2.27/v2.27-ReleaseNotes 213 if initcommon.IsMountReadOnly(vol, app.MountPoints) { 214 opts := []*unit.UnitOption{ 215 unit.NewUnitOption("Unit", "Description", fmt.Sprintf("Remount read-only unit for %s", wherePath)), 216 unit.NewUnitOption("Unit", "DefaultDependencies", "false"), 217 unit.NewUnitOption("Unit", "After", mu), 218 unit.NewUnitOption("Unit", "Wants", mu), 219 unit.NewUnitOption("Service", "ExecStart", fmt.Sprintf("/usr/bin/mount -o remount,ro %s", wherePath)), 220 unit.NewUnitOption("Install", "RequiredBy", mu), 221 } 222 223 remountUnitPath := filepath.Join(root, unitsDir, unit.UnitNamePathEscape(wherePath+"-remount.service")) 224 if err := writeUnit(opts, remountUnitPath); err != nil { 225 return err 226 } 227 } 228 } 229 return nil 230 } 231 232 // VolumesToKvmDiskArgs prepares argument list to be passed to lkvm to configure 233 // shared volumes (only for "host" kind). 234 // Example return is ["--9p,src/folder,9ptag"]. 235 func VolumesToKvmDiskArgs(volumes []types.Volume) []string { 236 var args []string 237 238 for _, vol := range volumes { 239 mountTag := vol.Name.String() // tag/channel name for virtio 240 if vol.Kind == "host" { 241 // eg. --9p=/home/jon/srcdir,tag 242 arg := "--9p=" + vol.Source + "," + mountTag 243 log.Printf("stage1: --disk argument: %#v\n", arg) 244 args = append(args, arg) 245 } 246 } 247 248 return args 249 }