github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/stage1/init/common/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 // Package common and the file kvm_mount.go provide 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 common 37 38 import ( 39 "fmt" 40 "io/ioutil" 41 "os" 42 "path/filepath" 43 "strings" 44 45 "github.com/appc/spec/schema" 46 "github.com/appc/spec/schema/types" 47 "github.com/coreos/go-systemd/unit" 48 "github.com/coreos/rkt/common" 49 "github.com/hashicorp/errwrap" 50 ) 51 52 const ( 53 // location within stage1 rootfs where shared volumes will be put 54 // (or empty directories for kind=empty) 55 stage1MntDir = "/mnt/" 56 ) 57 58 // serviceUnitName returns a systemd service unit name for the given app name. 59 // note: it was shamefully copy-pasted from stage1/init/path.go 60 // TODO: extract common functions from path.go 61 func serviceUnitName(appName types.ACName) string { 62 return appName.String() + ".service" 63 } 64 65 // installNewMountUnit creates and installs a new mount unit in the default 66 // systemd location (/usr/lib/systemd/system) inside the pod stage1 filesystem. 67 // root is pod's absolute stage1 path (from Pod.Root). 68 // beforeAndrequiredBy creates a systemd unit dependency (can be space separated 69 // for multi). 70 // It returns the name of the generated unit. 71 func installNewMountUnit(root, what, where, fsType, options, beforeAndrequiredBy, unitsDir string) (string, error) { 72 opts := []*unit.UnitOption{ 73 unit.NewUnitOption("Unit", "Description", fmt.Sprintf("Mount unit for %s", where)), 74 unit.NewUnitOption("Unit", "DefaultDependencies", "false"), 75 unit.NewUnitOption("Unit", "Before", beforeAndrequiredBy), 76 unit.NewUnitOption("Mount", "What", what), 77 unit.NewUnitOption("Mount", "Where", where), 78 unit.NewUnitOption("Mount", "Type", fsType), 79 unit.NewUnitOption("Mount", "Options", options), 80 unit.NewUnitOption("Install", "RequiredBy", beforeAndrequiredBy), 81 } 82 83 unitsPath := filepath.Join(root, unitsDir) 84 unitName := unit.UnitNamePathEscape(where + ".mount") 85 86 if err := writeUnit(opts, filepath.Join(unitsPath, unitName)); err != nil { 87 return "", err 88 } 89 diag.Printf("mount unit created: %q in %q (what=%q, where=%q)", unitName, unitsPath, what, where) 90 91 return unitName, nil 92 } 93 94 func writeUnit(opts []*unit.UnitOption, unitPath string) error { 95 unitBytes, err := ioutil.ReadAll(unit.Serialize(opts)) 96 if err != nil { 97 return errwrap.Wrap(fmt.Errorf("failed to serialize mount unit file to bytes %q", unitPath), err) 98 } 99 100 err = ioutil.WriteFile(unitPath, unitBytes, 0644) 101 if err != nil { 102 return errwrap.Wrap(fmt.Errorf("failed to create mount unit file %q", unitPath), err) 103 } 104 105 return nil 106 } 107 108 // PodToSystemdHostMountUnits create host shared remote file system 109 // mounts (using e.g. 9p) according to https://www.kernel.org/doc/Documentation/filesystems/9p.txt. 110 // Additionally it creates required directories in stage1MntDir and then prepares 111 // bind mount unit for each app. 112 // "root" parameter is stage1 root filesystem path. 113 // appNames are used to create before/required dependency between mount unit and 114 // app service units. 115 func PodToSystemdHostMountUnits(root string, volumes []types.Volume, appNames []types.ACName, unitsDir string) error { 116 // pod volumes need to mount p9 qemu mount_tags 117 for _, vol := range volumes { 118 // only host shared volumes 119 120 name := vol.Name.String() // acts as a mount tag 9p 121 122 // serviceNames for ordering and requirements dependency for apps 123 var serviceNames []string 124 for _, appName := range appNames { 125 serviceNames = append(serviceNames, serviceUnitName(appName)) 126 } 127 128 // for host kind we create a mount unit to mount host shared folder 129 if vol.Kind == "host" { 130 // /var/lib/.../pod/run/rootfs/mnt/{volumeName} 131 mountPoint := filepath.Join(root, stage1MntDir, name) 132 err := os.MkdirAll(mountPoint, 0700) 133 if err != nil { 134 return err 135 } 136 137 _, err = installNewMountUnit(root, 138 name, // what (source) in 9p it is a channel tag which equals to volume.Name/mountPoint.name 139 filepath.Join(stage1MntDir, name), // where - destination 140 "9p", // fsType 141 "trans=virtio", // 9p specific options 142 strings.Join(serviceNames, " "), // space separated list of services for unit dependency 143 unitsDir, 144 ) 145 if err != nil { 146 return err 147 } 148 } 149 } 150 151 return nil 152 } 153 154 // AppToSystemdMountUnits prepare bind mount unit for empty or host kind mounting 155 // between stage1 rootfs and chrooted filesystem for application 156 func AppToSystemdMountUnits(root string, appName types.ACName, volumes []types.Volume, ra *schema.RuntimeApp, unitsDir string) error { 157 app := ra.App 158 159 vols := make(map[types.ACName]types.Volume) 160 for _, v := range volumes { 161 vols[v.Name] = v 162 } 163 164 mounts := GenerateMounts(ra, vols) 165 for _, m := range mounts { 166 vol := vols[m.Volume] 167 168 // source relative to stage1 rootfs to relative pod root 169 whatPath := filepath.Join(stage1MntDir, vol.Name.String()) 170 whatFullPath := filepath.Join(root, whatPath) 171 172 if vol.Kind == "empty" { 173 diag.Printf("creating an empty volume folder for sharing: %q", whatFullPath) 174 err := os.MkdirAll(whatFullPath, 0700) 175 if err != nil { 176 return err 177 } 178 } 179 180 // destination relative to stage1 rootfs and relative to pod root 181 wherePath := filepath.Join(common.RelAppRootfsPath(appName), m.Path) 182 whereFullPath := filepath.Join(root, wherePath) 183 184 // assertion to make sure that "what" exists (created earlier by PodToSystemdHostMountUnits) 185 diag.Printf("checking required source path: %q", whatFullPath) 186 if _, err := os.Stat(whatFullPath); os.IsNotExist(err) { 187 return fmt.Errorf("bug: missing source for volume %v", vol.Name) 188 } 189 190 // optionally prepare app directory 191 diag.Printf("optionally preparing destination path: %q", whereFullPath) 192 err := os.MkdirAll(whereFullPath, 0700) 193 if err != nil { 194 return errwrap.Wrap(fmt.Errorf("failed to prepare dir for mount %v", m.Volume), err) 195 } 196 197 // install new mount unit for bind mount /mnt/volumeName -> /opt/stage2/{app-id}/rootfs/{{mountPoint.Path}} 198 mu, err := installNewMountUnit( 199 root, // where put a mount unit 200 whatPath, // what - stage1 rootfs /mnt/VolumeName 201 wherePath, // where - inside chroot app filesystem 202 "bind", // fstype 203 "bind", // options 204 serviceUnitName(appName), 205 unitsDir, 206 ) 207 if err != nil { 208 return errwrap.Wrap(fmt.Errorf("cannot install new mount unit for app %q", appName.String()), err) 209 } 210 211 // TODO(iaguis) when we update util-linux to 2.27, this code can go 212 // away and we can bind-mount RO with one unit file. 213 // http://ftp.kernel.org/pub/linux/utils/util-linux/v2.27/v2.27-ReleaseNotes 214 if IsMountReadOnly(vol, app.MountPoints) { 215 opts := []*unit.UnitOption{ 216 unit.NewUnitOption("Unit", "Description", fmt.Sprintf("Remount read-only unit for %s", wherePath)), 217 unit.NewUnitOption("Unit", "DefaultDependencies", "false"), 218 unit.NewUnitOption("Unit", "After", mu), 219 unit.NewUnitOption("Unit", "Wants", mu), 220 unit.NewUnitOption("Service", "ExecStart", fmt.Sprintf("/usr/bin/mount -o remount,ro %s", wherePath)), 221 unit.NewUnitOption("Install", "RequiredBy", mu), 222 } 223 224 remountUnitPath := filepath.Join(root, unitsDir, unit.UnitNamePathEscape(wherePath+"-remount.service")) 225 if err := writeUnit(opts, remountUnitPath); err != nil { 226 return err 227 } 228 } 229 } 230 return nil 231 } 232 233 // VolumesToKvmDiskArgs prepares argument list to be passed to lkvm to configure 234 // shared volumes (only for "host" kind). 235 // Example return is ["--9p,src/folder,9ptag"]. 236 func VolumesToKvmDiskArgs(volumes []types.Volume) []string { 237 var args []string 238 239 for _, vol := range volumes { 240 mountTag := vol.Name.String() // tag/channel name for virtio 241 if vol.Kind == "host" { 242 // eg. --9p=/home/jon/srcdir,tag 243 arg := "--9p=" + vol.Source + "," + mountTag 244 diag.Printf("--disk argument: %#v", arg) 245 args = append(args, arg) 246 } 247 } 248 249 return args 250 }