Skip to content

Commit 5ec9372

Browse files
committed
Merge branch '156-mountpoints' into 'master'
fix: clone volumes and socket paths (#156): - rewrite clone mount points - reset socket paths before clone starting See merge request postgres-ai/database-lab!166
2 parents f83b7aa + 62b56a7 commit 5ec9372

File tree

2 files changed

+113
-29
lines changed

2 files changed

+113
-29
lines changed

pkg/retrieval/engine/postgres/tools/tools.go

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -129,24 +129,7 @@ func AddVolumesToHostConfig(ctx context.Context, dockerClient *client.Client, ho
129129
return err
130130
}
131131

132-
for _, mountPoint := range inspection.Mounts {
133-
// Rewrite mounting to data directory.
134-
if strings.HasPrefix(dataDir, mountPoint.Destination) {
135-
suffix := strings.TrimPrefix(dataDir, mountPoint.Destination)
136-
mountPoint.Source = path.Join(mountPoint.Source, suffix)
137-
mountPoint.Destination = dataDir
138-
}
139-
140-
hostConfig.Mounts = append(hostConfig.Mounts, mount.Mount{
141-
Type: mountPoint.Type,
142-
Source: mountPoint.Source,
143-
Target: mountPoint.Destination,
144-
ReadOnly: !mountPoint.RW,
145-
BindOptions: &mount.BindOptions{
146-
Propagation: mountPoint.Propagation,
147-
},
148-
})
149-
}
132+
hostConfig.Mounts = GetMountsFromMountPoints(dataDir, inspection.Mounts)
150133

151134
log.Dbg(hostConfig.Mounts)
152135
} else {
@@ -160,6 +143,32 @@ func AddVolumesToHostConfig(ctx context.Context, dockerClient *client.Client, ho
160143
return nil
161144
}
162145

146+
// GetMountsFromMountPoints creates a list of mounts.
147+
func GetMountsFromMountPoints(dataDir string, mountPoints []types.MountPoint) []mount.Mount {
148+
mounts := make([]mount.Mount, 0, len(mountPoints))
149+
150+
for _, mountPoint := range mountPoints {
151+
// Rewrite mounting to data directory.
152+
if strings.HasPrefix(dataDir, mountPoint.Destination) {
153+
suffix := strings.TrimPrefix(dataDir, mountPoint.Destination)
154+
mountPoint.Source = path.Join(mountPoint.Source, suffix)
155+
mountPoint.Destination = dataDir
156+
}
157+
158+
mounts = append(mounts, mount.Mount{
159+
Type: mountPoint.Type,
160+
Source: mountPoint.Source,
161+
Target: mountPoint.Destination,
162+
ReadOnly: !mountPoint.RW,
163+
BindOptions: &mount.BindOptions{
164+
Propagation: mountPoint.Propagation,
165+
},
166+
})
167+
}
168+
169+
return mounts
170+
}
171+
163172
// RunPostgres runs Postgres inside.
164173
func RunPostgres(ctx context.Context, dockerClient *client.Client, containerID, dataDir string) error {
165174
// Set permissions.

pkg/services/provision/docker/docker.go

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66
package docker
77

88
import (
9+
"encoding/json"
910
"fmt"
1011
"os"
12+
"path"
1113
"strconv"
1214
"strings"
1315

16+
"github.com/docker/docker/api/types"
1417
"github.com/pkg/errors"
1518
"github.com/shirou/gopsutil/host"
1619

20+
"gitlab.com/postgres-ai/database-lab/pkg/retrieval/engine/postgres/tools"
1721
"gitlab.com/postgres-ai/database-lab/pkg/services/provision/resources"
1822
"gitlab.com/postgres-ai/database-lab/pkg/services/provision/runners"
1923
)
@@ -30,30 +34,101 @@ func RunContainer(r runners.Runner, c *resources.AppConfig) (string, error) {
3034
}
3135

3236
// Directly mount PGDATA if Database Lab is running without any virtualization.
33-
socketVolume := fmt.Sprintf("--volume %s:%s", c.Datadir, c.Datadir)
37+
volumes := []string{fmt.Sprintf("--volume %s:%s", c.Datadir, c.Datadir)}
3438

3539
if hostInfo.VirtualizationRole == "guest" {
36-
// Use volumes from the Database Lab instance if it's running inside Docker container.
37-
socketVolume = "--volumes-from=" + hostInfo.Hostname
40+
// Build custom mounts rely on mounts of the Database Lab instance if it's running inside Docker container.
41+
// We cannot use --volumes-from because it removes the ZFS mount point.
42+
volumes, err = buildMountVolumes(r, hostInfo.Hostname, c.Datadir, c.UnixSocketCloneDir)
43+
if err != nil {
44+
return "", errors.Wrap(err, "failed to detect container volumes")
45+
}
3846
}
3947

4048
if err := createSocketCloneDir(c.UnixSocketCloneDir); err != nil {
4149
return "", errors.Wrap(err, "failed to create socket clone directory")
4250
}
4351

44-
dockerRunCmd := "docker run " +
45-
"--name " + c.CloneName + " " +
46-
"--detach " +
47-
"--publish " + strconv.Itoa(int(c.Port)) + ":5432 " +
48-
"--env PGDATA=" + c.Datadir + " " + socketVolume + " " +
49-
"--label " + labelClone + " " +
50-
"--label " + c.ClonePool + " " +
51-
c.DockerImage + " -k " + c.UnixSocketCloneDir
52+
dockerRunCmd := strings.Join([]string{
53+
"docker run",
54+
"--name", c.CloneName,
55+
"--detach",
56+
"--publish", strconv.Itoa(int(c.Port)) + ":5432",
57+
"--env", "PGDATA=" + c.Datadir,
58+
strings.Join(volumes, " "),
59+
"--label", labelClone,
60+
"--label", c.ClonePool,
61+
c.DockerImage,
62+
"-k", c.UnixSocketCloneDir,
63+
}, " ")
5264

5365
return r.Run(dockerRunCmd, true)
5466
}
5567

68+
func buildMountVolumes(r runners.Runner, containerID, dataDir, cloneDir string) ([]string, error) {
69+
inspectCmd := "docker inspect -f '{{ json .Mounts }}' " + containerID
70+
71+
var mountPoints []types.MountPoint
72+
73+
out, err := r.Run(inspectCmd, true)
74+
if err != nil {
75+
return nil, errors.Wrap(err, "failed to get container mounts")
76+
}
77+
78+
if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &mountPoints); err != nil {
79+
return nil, errors.Wrap(err, "failed to interpret mount paths")
80+
}
81+
82+
mounts := tools.GetMountsFromMountPoints(dataDir, mountPoints)
83+
volumes := make([]string, 0, len(mounts))
84+
85+
for _, mount := range mounts {
86+
// Add extra mount for socket directories.
87+
// TODO (akartasov): Add mountDir to global config.
88+
if mount.Target == dataDir {
89+
volumes = append(volumes, buildSocketMount(mount.Source, dataDir, cloneDir))
90+
}
91+
92+
volume := fmt.Sprintf("--volume %s:%s", mount.Source, mount.Target)
93+
94+
if mount.BindOptions != nil && mount.BindOptions.Propagation != "" {
95+
volume += ":" + string(mount.BindOptions.Propagation)
96+
}
97+
98+
volumes = append(volumes, volume)
99+
}
100+
101+
return volumes, nil
102+
}
103+
104+
// buildSocketMount builds a socket directory mounting rely on dataDir mounting.
105+
func buildSocketMount(hostDataDir, dataDir, cloneDir string) string {
106+
mountDir := cloneDir
107+
108+
// Discover the common path prefix supposing it is the mount point.
109+
for mountDir != "." {
110+
mountDir, _ = path.Split(mountDir)
111+
112+
if strings.HasPrefix(dataDir, mountDir) {
113+
break
114+
}
115+
116+
mountDir = path.Clean(mountDir)
117+
}
118+
119+
clonePath := strings.TrimPrefix(cloneDir, mountDir)
120+
dataPath := strings.TrimPrefix(dataDir, mountDir)
121+
internalMount := strings.TrimSuffix(hostDataDir, dataPath)
122+
internalMountPath := path.Join(internalMount, clonePath)
123+
124+
return fmt.Sprintf(" --volume %s:%s:rshared", internalMountPath, cloneDir)
125+
}
126+
56127
func createSocketCloneDir(socketCloneDir string) error {
128+
if err := os.RemoveAll(socketCloneDir); err != nil {
129+
return err
130+
}
131+
57132
if err := os.MkdirAll(socketCloneDir, 0777); err != nil {
58133
return err
59134
}

0 commit comments

Comments
 (0)