Skip to content
This repository was archived by the owner on Feb 8, 2021. It is now read-only.

Commit 0158453

Browse files
committed
Merge branch 'master' into integration-test
2 parents 8754a3f + e60720c commit 0158453

File tree

18 files changed

+2452
-6
lines changed

18 files changed

+2452
-6
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ Hyper_ client for Mac/Linux
44

55
How to build
66
```
7-
$ mkdir $GOPATH/hyperhq/
8-
$ cd $GOPATH/hyperhq/
7+
$ mkdir $GOPATH/src/github.com/hyperhq/
8+
$ cd $GOPATH/src/github.com/hyperhq/
99
$ git clone https://github.com/hyperhq/hypercli hypercli
1010
$ cd hypercli
1111
$ ./build-hyperserve-client.sh

api/client/exec.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package client
33
import (
44
"fmt"
55
"io"
6+
"strings"
7+
"time"
68

79
"github.com/Sirupsen/logrus"
10+
"github.com/docker/engine-api/types"
811
Cli "github.com/hyperhq/hypercli/cli"
912
flag "github.com/hyperhq/hypercli/pkg/mflag"
1013
"github.com/hyperhq/hypercli/pkg/promise"
11-
"github.com/docker/engine-api/types"
1214
)
1315

1416
// CmdExec runs a command in a running container.
@@ -162,3 +164,45 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*types.ExecConfig, error) {
162164

163165
return execConfig, nil
164166
}
167+
168+
func (cli *DockerCli) ExecCmd(user, contID string, cmd []string) (string, error) {
169+
execConfig := &types.ExecConfig{
170+
User: user,
171+
Container: contID,
172+
Detach: true,
173+
Cmd: cmd,
174+
}
175+
execCreateResponse, err := cli.client.ContainerExecCreate(*execConfig)
176+
if err != nil {
177+
return "", err
178+
}
179+
execID := execCreateResponse.ID
180+
if execID == "" {
181+
err = fmt.Errorf("Failed to exec %s: Empty exec ID", strings.Join(cmd, " "))
182+
return "", err
183+
}
184+
execStartCheck := types.ExecStartCheck{Detach: execConfig.Detach}
185+
if err := cli.client.ContainerExecStart(execID, execStartCheck); err != nil {
186+
return "", err
187+
}
188+
189+
return execID, nil
190+
}
191+
192+
func (cli *DockerCli) WaitExec(execID string) error {
193+
for {
194+
running, status, err := getExecExitCode(cli, execID)
195+
switch {
196+
case err != nil:
197+
return err
198+
case running:
199+
time.Sleep(100 * time.Millisecond)
200+
case status != 0:
201+
err = fmt.Errorf("Failed to init volume: %d", status)
202+
return err
203+
case status == 0:
204+
return nil
205+
}
206+
}
207+
208+
}

api/client/fip.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,39 @@ func fipUsage() string {
160160
help += fmt.Sprintf("\nRun 'hyper fip COMMAND --help' for more information on a command.")
161161
return help
162162
}
163+
164+
// Allocate and associate a fip
165+
func (cli *DockerCli) associateNewFip(contID string) (string, error) {
166+
fips, err := cli.client.FipAllocate("1")
167+
if err != nil {
168+
return "", err
169+
}
170+
171+
for _, ip := range fips {
172+
err = cli.client.FipAssociate(ip, contID)
173+
if err != nil {
174+
go func() {
175+
cli.client.FipRelease(ip)
176+
}()
177+
return "", err
178+
}
179+
return ip, nil
180+
}
181+
182+
return "", fmt.Errorf("Server failed to create new fip")
183+
}
184+
185+
// Release a fip
186+
func (cli *DockerCli) releaseFip(ip string) error {
187+
return cli.client.FipRelease(ip)
188+
}
189+
190+
// Disassociate and release a fip
191+
func (cli *DockerCli) releaseContainerFip(contID string) error {
192+
ip, err := cli.client.FipDisassociate(contID)
193+
if err != nil {
194+
return err
195+
}
196+
197+
return cli.client.FipRelease(ip)
198+
}

api/client/load.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package client
22

33
import (
4+
"errors"
45
"io"
56

67
Cli "github.com/hyperhq/hypercli/cli"
@@ -20,6 +21,10 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
2021
cmd.Require(flag.Exact, 0)
2122
cmd.ParseFlags(args, true)
2223

24+
if *infile == "" {
25+
return errors.New("remote archive must be specified via --input")
26+
}
27+
2328
var input struct {
2429
FromSrc string `json:"fromSrc"`
2530
Quiet bool `json:"quiet"`

api/client/run.go

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import (
55
"io"
66
"os"
77
"runtime"
8+
"strconv"
89
"strings"
910

1011
"github.com/Sirupsen/logrus"
1112
"github.com/docker/engine-api/types"
13+
"github.com/docker/engine-api/types/container"
14+
networktypes "github.com/docker/engine-api/types/network"
1215
"github.com/docker/libnetwork/resolvconf/dns"
1316
Cli "github.com/hyperhq/hypercli/cli"
1417
derr "github.com/hyperhq/hypercli/errors"
@@ -17,8 +20,15 @@ import (
1720
"github.com/hyperhq/hypercli/pkg/signal"
1821
"github.com/hyperhq/hypercli/pkg/stringid"
1922
runconfigopts "github.com/hyperhq/hypercli/runconfig/opts"
23+
"github.com/satori/go.uuid"
2024
)
2125

26+
type InitVolume struct {
27+
Source string
28+
Destination string
29+
Name string
30+
}
31+
2232
func (cid *cidFile) Close() error {
2333
cid.file.Close()
2434

@@ -62,6 +72,179 @@ func runStartContainerErr(err error) error {
6272
return statusError
6373
}
6474

75+
func parseProtoAndLocalBind(bind string) (string, string, bool) {
76+
switch {
77+
case strings.HasPrefix(bind, "git://"):
78+
fallthrough
79+
case strings.HasPrefix(bind, "http://"):
80+
fallthrough
81+
case strings.HasPrefix(bind, "https://"):
82+
if strings.Count(bind, ":") < 2 {
83+
return "", "", false
84+
}
85+
case strings.HasPrefix(bind, "/"):
86+
if strings.Count(bind, ":") < 1 {
87+
return "", "", false
88+
}
89+
default:
90+
return "", "", false
91+
}
92+
93+
pos := strings.LastIndex(bind, ":")
94+
if pos < 0 || pos >= len(bind)-1 {
95+
return "", "", false
96+
}
97+
98+
return bind[:pos], bind[pos+1:], true
99+
}
100+
101+
func checkSourceType(source string) string {
102+
part := strings.Split(source, ":")
103+
count := len(part)
104+
switch {
105+
case strings.HasPrefix(source, "git://") || strings.HasSuffix(source, ".git") ||
106+
(count >= 2 && strings.HasSuffix(part[count-2], ".git")):
107+
return "git"
108+
case strings.HasPrefix(source, "http://"):
109+
fallthrough
110+
case strings.HasPrefix(source, "https://"):
111+
return "http"
112+
case strings.HasPrefix(source, "/"):
113+
return "local"
114+
default:
115+
return "unknown"
116+
}
117+
}
118+
119+
func (cli *DockerCli) initSpecialVolumes(config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, initvols []*InitVolume) error {
120+
const INIT_VOLUME_PATH = "/vol/"
121+
const INIT_VOLUME_IMAGE = "hyperhq/volume_uploader:latest"
122+
var (
123+
initConfig *container.Config
124+
initHostConfig *container.HostConfig
125+
errCh chan error
126+
execCount uint32
127+
fip string
128+
)
129+
130+
initConfig = &container.Config{
131+
User: config.User,
132+
Image: INIT_VOLUME_IMAGE,
133+
StopSignal: config.StopSignal,
134+
}
135+
136+
initHostConfig = &container.HostConfig{
137+
Binds: make([]string, 0),
138+
DNS: hostConfig.DNS,
139+
DNSOptions: hostConfig.DNSOptions,
140+
DNSSearch: hostConfig.DNSSearch,
141+
ExtraHosts: hostConfig.ExtraHosts,
142+
}
143+
144+
for _, vol := range initvols {
145+
initHostConfig.Binds = append(initHostConfig.Binds, vol.Name+":"+INIT_VOLUME_PATH+vol.Destination)
146+
}
147+
passwd := uuid.NewV1()
148+
initConfig.Env = append(config.Env, "ROOTPASSWORD="+passwd.String(), "LOCALROOT="+INIT_VOLUME_PATH)
149+
150+
createResponse, err := cli.createContainer(initConfig, initHostConfig, networkingConfig, hostConfig.ContainerIDFile, "")
151+
if err != nil {
152+
return err
153+
}
154+
defer func() {
155+
if err != nil {
156+
if _, rmErr := cli.removeContainer(createResponse.ID, true, false, true); rmErr != nil {
157+
fmt.Fprintf(cli.err, "clean up init container failed: %s\n", rmErr.Error())
158+
}
159+
}
160+
if fip != "" {
161+
if rmErr := cli.releaseFip(fip); rmErr != nil {
162+
fmt.Fprintf(cli.err, "failed to clean up container fip %s: %s\n", fip, rmErr.Error())
163+
}
164+
}
165+
}()
166+
167+
if err = cli.client.ContainerStart(createResponse.ID); err != nil {
168+
return err
169+
}
170+
171+
errCh = make(chan error, len(initvols))
172+
for _, vol := range initvols {
173+
var cmd []string
174+
volType := checkSourceType(vol.Source)
175+
switch volType {
176+
case "git":
177+
cmd = append(cmd, "git", "clone", vol.Source, INIT_VOLUME_PATH+vol.Destination)
178+
case "http":
179+
parts := strings.Split(vol.Source, "/")
180+
cmd = append(cmd, "wget", "--no-check-certificate", "--tries=5", "--mirror", "--no-host-directories", "--cut-dirs="+strconv.Itoa(len(parts)), vol.Source, "--directory-prefix="+INIT_VOLUME_PATH+vol.Destination)
181+
case "local":
182+
execCount++
183+
if fip == "" {
184+
fip, err = cli.associateNewFip(createResponse.ID)
185+
if err != nil {
186+
return err
187+
}
188+
}
189+
go func(vol *InitVolume) {
190+
err := cli.uploadLocalResource(vol.Source, INIT_VOLUME_PATH+vol.Destination, fip, "root", passwd.String())
191+
if err != nil {
192+
err = fmt.Errorf("Failed to upload %s: %s", vol.Source, err.Error())
193+
}
194+
errCh <- err
195+
}(vol)
196+
default:
197+
continue
198+
}
199+
if len(cmd) == 0 {
200+
continue
201+
}
202+
203+
execCount++
204+
go func() {
205+
execID, err := cli.ExecCmd(initConfig.User, createResponse.ID, cmd)
206+
if err != nil {
207+
errCh <- err
208+
} else {
209+
errCh <- cli.WaitExec(execID)
210+
}
211+
}()
212+
}
213+
214+
// wait results
215+
for ; execCount > 0; execCount-- {
216+
err = <-errCh
217+
if err != nil {
218+
return err
219+
}
220+
}
221+
222+
// release fip
223+
if fip != "" {
224+
if err = cli.releaseContainerFip(createResponse.ID); err != nil {
225+
return err
226+
}
227+
fip = ""
228+
}
229+
230+
// Need to sync before tearing down container because data might be still cached
231+
syncCmd := []string{"sync"}
232+
execID, err := cli.ExecCmd(initConfig.User, createResponse.ID, syncCmd)
233+
if err != nil {
234+
return err
235+
}
236+
if err = cli.WaitExec(execID); err != nil {
237+
return err
238+
}
239+
240+
_, err = cli.removeContainer(createResponse.ID, false, false, true)
241+
if err != nil {
242+
return err
243+
}
244+
245+
return nil
246+
}
247+
65248
// CmdRun runs a command in a new container.
66249
//
67250
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
@@ -147,11 +330,50 @@ func (cli *DockerCli) CmdRun(args ...string) error {
147330
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
148331
}
149332

333+
// Check/create protocol and local volume
334+
var initvols []*InitVolume
335+
defer func() {
336+
for _, vol := range initvols {
337+
cli.client.VolumeRemove(vol.Name)
338+
}
339+
}()
340+
for idx, bind := range hostConfig.Binds {
341+
if source, dest, ok := parseProtoAndLocalBind(bind); ok {
342+
volReq := types.VolumeCreateRequest{
343+
Driver: "hyper",
344+
Labels: map[string]string{
345+
"source": source,
346+
}}
347+
if vol, err := cli.client.VolumeCreate(volReq); err != nil {
348+
cmd.ReportError(err.Error(), true)
349+
return runStartContainerErr(err)
350+
} else {
351+
initvols = append(initvols, &InitVolume{
352+
Source: source,
353+
Destination: dest,
354+
Name: vol.Name,
355+
})
356+
hostConfig.Binds[idx] = vol.Name + ":" + dest
357+
}
358+
}
359+
}
360+
361+
// initialize special volumes
362+
if len(initvols) > 0 {
363+
err := cli.initSpecialVolumes(config, hostConfig, networkingConfig, initvols)
364+
if err != nil {
365+
cmd.ReportError(err.Error(), true)
366+
return runStartContainerErr(err)
367+
}
368+
}
369+
150370
createResponse, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
151371
if err != nil {
152372
cmd.ReportError(err.Error(), true)
153373
return runStartContainerErr(err)
154374
}
375+
initvols = nil
376+
155377
if sigProxy {
156378
sigc := cli.forwardAllSignals(createResponse.ID)
157379
defer signal.StopCatch(sigc)

0 commit comments

Comments
 (0)