From 8f14953d9431d1f0ab2a314d2873b53888f9a1f7 Mon Sep 17 00:00:00 2001 From: Jana Radhakrishnan Date: Sat, 7 Feb 2015 00:14:41 +0000 Subject: [PATCH 1/5] Add powerstrip support Signed-off-by: Jana Radhakrishnan --- Godeps/Godeps.json | 8 + .../samalba/dockerclient/.gitignore | 22 + .../github.com/samalba/dockerclient/LICENSE | 202 ++++++++++ .../github.com/samalba/dockerclient/README.md | 63 +++ .../github.com/samalba/dockerclient/auth.go | 21 + .../samalba/dockerclient/auth_test.go | 15 + .../samalba/dockerclient/dockerclient.go | 380 ++++++++++++++++++ .../samalba/dockerclient/dockerclient_test.go | 153 +++++++ .../samalba/dockerclient/engine_mock_test.go | 210 ++++++++++ .../samalba/dockerclient/example_responses.go | 11 + .../samalba/dockerclient/examples/events.go | 32 ++ .../samalba/dockerclient/interface.go | 29 ++ .../samalba/dockerclient/mockclient/mock.go | 109 +++++ .../dockerclient/mockclient/mock_test.go | 32 ++ .../github.com/samalba/dockerclient/types.go | 168 ++++++++ .../github.com/samalba/dockerclient/utils.go | 33 ++ .../vishvananda/netlink/link_test.go | 2 +- .../vishvananda/netlink/netlink_test.go | 2 +- .../src/github.com/vishvananda/netns/LICENSE | 192 +++++++++ .../github.com/vishvananda/netns/README.md | 49 +++ .../src/github.com/vishvananda/netns/netns.go | 66 +++ .../vishvananda/netns/netns_linux.go | 196 +++++++++ .../vishvananda/netns/netns_linux_386.go | 5 + .../vishvananda/netns/netns_linux_amd.go | 5 + .../vishvananda/netns/netns_linux_arm.go | 5 + .../vishvananda/netns/netns_test.go | 44 ++ .../vishvananda/netns/netns_unspecified.go | 35 ++ Vagrantfile | 5 +- adapters.yml | 8 + daemon/api.go | 133 ++++++ daemon/bridge.go | 39 +- daemon/daemon.go | 5 + scripts/socketplane.sh | 71 ++-- 33 files changed, 2318 insertions(+), 32 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/.gitignore create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/LICENSE create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/README.md create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/auth.go create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/auth_test.go create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/engine_mock_test.go create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/example_responses.go create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/examples/events.go create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock_test.go create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/types.go create mode 100644 Godeps/_workspace/src/github.com/samalba/dockerclient/utils.go create mode 100644 Godeps/_workspace/src/github.com/vishvananda/netns/LICENSE create mode 100644 Godeps/_workspace/src/github.com/vishvananda/netns/README.md create mode 100644 Godeps/_workspace/src/github.com/vishvananda/netns/netns.go create mode 100644 Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux.go create mode 100644 Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_386.go create mode 100644 Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_amd.go create mode 100644 Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_arm.go create mode 100644 Godeps/_workspace/src/github.com/vishvananda/netns/netns_test.go create mode 100644 Godeps/_workspace/src/github.com/vishvananda/netns/netns_unspecified.go create mode 100644 adapters.yml diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index aea81ba..1cc9267 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -168,6 +168,10 @@ "Comment": "v2.0.1-6-g44cb478", "Rev": "44cb4788b2ec3c3d158dd3d1b50aba7d66f4b59a" }, + { + "ImportPath": "github.com/samalba/dockerclient", + "Rev": "7e4366cfab2f2b44fcb493bee93a156a763d58b6" + }, { "ImportPath": "github.com/socketplane/bonjour", "Comment": "v0.1.2", @@ -191,6 +195,10 @@ "ImportPath": "github.com/vishvananda/netlink", "Rev": "80a80badbda02c350deb690b00fe024555d2d088" }, + { + "ImportPath": "github.com/vishvananda/netns", + "Rev": "e14a2d4e90ae568a5a78b248da5579f289e25925" + }, { "ImportPath": "golang.org/x/net/internal/iana", "Rev": "9dd48c277bcb2bb2cc3eb6a6368a486a567d3562" diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/.gitignore b/Godeps/_workspace/src/github.com/samalba/dockerclient/.gitignore new file mode 100644 index 0000000..0026861 --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/LICENSE b/Godeps/_workspace/src/github.com/samalba/dockerclient/LICENSE new file mode 100644 index 0000000..00e1edb --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 Sam Alba + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/README.md b/Godeps/_workspace/src/github.com/samalba/dockerclient/README.md new file mode 100644 index 0000000..26f2528 --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/README.md @@ -0,0 +1,63 @@ +Docker client library in Go +=========================== +[![GoDoc](http://godoc.org/github.com/samalba/dockerclient?status.png)](http://godoc.org/github.com/samalba/dockerclient) + +Well maintained docker client library. + +Example: + +```go +package main + +import ( + "github.com/samalba/dockerclient" + "log" + "time" +) + +// Callback used to listen to Docker's events +func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{}) { + log.Printf("Received event: %#v\n", *event) +} + +func main() { + // Init the client + docker, _ := dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil) + + // Get only running containers + containers, err := docker.ListContainers(false) + if err != nil { + log.Fatal(err) + } + for _, c := range containers { + log.Println(c.Id, c.Names) + } + + // Inspect the first container returned + if len(containers) > 0 { + id := containers[0].Id + info, _ := docker.InspectContainer(id) + log.Println(info) + } + + // Create a container + containerConfig := &dockerclient.ContainerConfig{Image: "ubuntu:12.04", Cmd: []string{"bash"}} + containerId, err := docker.CreateContainer(containerConfig) + if err != nil { + log.Fatal(err) + } + + // Start the container + err = docker.StartContainer(containerId) + if err != nil { + log.Fatal(err) + } + + // Stop the container (with 5 seconds timeout) + docker.StopContainer(containerId, 5) + + // Listen to events + docker.StartMonitorEvents(eventCallback, nil) + time.Sleep(3600 * time.Second) +} +``` diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/auth.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/auth.go new file mode 100644 index 0000000..022d3dd --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/auth.go @@ -0,0 +1,21 @@ +package dockerclient + +import ( + "bytes" + "encoding/base64" + "encoding/json" +) + +// AuthConfig hold parameters for authenticating with the docker registry +type AuthConfig struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Email string `json:"email,omitempty"` +} + +// encode the auth configuration struct into base64 for the X-Registry-Auth header +func (c *AuthConfig) encode() string { + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(c) + return base64.URLEncoding.EncodeToString(buf.Bytes()) +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/auth_test.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/auth_test.go new file mode 100644 index 0000000..f6eac99 --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/auth_test.go @@ -0,0 +1,15 @@ +package dockerclient + +import ( + "testing" +) + +func TestAuthEncode(t *testing.T) { + a := AuthConfig{Username: "foo", Password: "password", Email: "bar@baz.com"} + expected := "eyJ1c2VybmFtZSI6ImZvbyIsInBhc3N3b3JkIjoicGFzc3dvcmQiLCJlbWFpbCI6ImJhckBiYXouY29tIn0K" + got := a.encode() + + if expected != got { + t.Errorf("testAuthEncode failed. Expected [%s] got [%s]", expected, got) + } +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go new file mode 100644 index 0000000..0bc2018 --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go @@ -0,0 +1,380 @@ +package dockerclient + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "strconv" + "strings" + "sync/atomic" + "time" +) + +const ( + APIVersion = "v1.15" +) + +var ( + ErrNotFound = errors.New("Not found") + + defaultTimeout = 30 * time.Second +) + +type DockerClient struct { + URL *url.URL + HTTPClient *http.Client + TLSConfig *tls.Config + monitorEvents int32 +} + +type Error struct { + StatusCode int + Status string + msg string +} + +func (e Error) Error() string { + return fmt.Sprintf("%s: %s", e.Status, e.msg) +} + +func NewDockerClient(daemonUrl string, tlsConfig *tls.Config) (*DockerClient, error) { + return NewDockerClientTimeout(daemonUrl, tlsConfig, time.Duration(defaultTimeout)) +} + +func NewDockerClientTimeout(daemonUrl string, tlsConfig *tls.Config, timeout time.Duration) (*DockerClient, error) { + u, err := url.Parse(daemonUrl) + if err != nil { + return nil, err + } + if u.Scheme == "" || u.Scheme == "tcp" { + if tlsConfig == nil { + u.Scheme = "http" + } else { + u.Scheme = "https" + } + } + httpClient := newHTTPClient(u, tlsConfig, timeout) + return &DockerClient{u, httpClient, tlsConfig, 0}, nil +} + +func (client *DockerClient) doRequest(method string, path string, body []byte, headers map[string]string) ([]byte, error) { + b := bytes.NewBuffer(body) + req, err := http.NewRequest(method, client.URL.String()+path, b) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + if headers != nil { + for header, value := range headers { + req.Header.Add(header, value) + } + } + resp, err := client.HTTPClient.Do(req) + if err != nil { + if !strings.Contains(err.Error(), "connection refused") && client.TLSConfig == nil { + return nil, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err) + } + return nil, err + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode == 404 { + return nil, ErrNotFound + } + if resp.StatusCode >= 400 { + return nil, Error{StatusCode: resp.StatusCode, Status: resp.Status, msg: string(data)} + } + return data, nil +} + +func (client *DockerClient) Info() (*Info, error) { + uri := fmt.Sprintf("/%s/info", APIVersion) + data, err := client.doRequest("GET", uri, nil, nil) + if err != nil { + return nil, err + } + ret := &Info{} + err = json.Unmarshal(data, &ret) + if err != nil { + return nil, err + } + return ret, nil +} + +func (client *DockerClient) ListContainers(all bool, size bool, filters string) ([]Container, error) { + argAll := 0 + if all == true { + argAll = 1 + } + showSize := 0 + if size == true { + showSize = 1 + } + uri := fmt.Sprintf("/%s/containers/json?all=%d&size=%d", APIVersion, argAll, showSize) + + if filters != "" { + uri += "&filters=" + filters + } + + data, err := client.doRequest("GET", uri, nil, nil) + if err != nil { + return nil, err + } + ret := []Container{} + err = json.Unmarshal(data, &ret) + if err != nil { + return nil, err + } + return ret, nil +} + +func (client *DockerClient) InspectContainer(id string) (*ContainerInfo, error) { + uri := fmt.Sprintf("/%s/containers/%s/json", APIVersion, id) + data, err := client.doRequest("GET", uri, nil, nil) + if err != nil { + return nil, err + } + info := &ContainerInfo{} + err = json.Unmarshal(data, info) + if err != nil { + return nil, err + } + return info, nil +} + +func (client *DockerClient) CreateContainer(config *ContainerConfig, name string) (string, error) { + data, err := json.Marshal(config) + if err != nil { + return "", err + } + uri := fmt.Sprintf("/%s/containers/create", APIVersion) + if name != "" { + v := url.Values{} + v.Set("name", name) + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + data, err = client.doRequest("POST", uri, data, nil) + if err != nil { + return "", err + } + result := &RespContainersCreate{} + err = json.Unmarshal(data, result) + if err != nil { + return "", err + } + return result.Id, nil +} + +func (client *DockerClient) ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error) { + v := url.Values{} + v.Add("follow", strconv.FormatBool(options.Follow)) + v.Add("stdout", strconv.FormatBool(options.Stdout)) + v.Add("stderr", strconv.FormatBool(options.Stderr)) + v.Add("timestamps", strconv.FormatBool(options.Timestamps)) + if options.Tail > 0 { + v.Add("tail", strconv.FormatInt(options.Tail, 10)) + } + + uri := fmt.Sprintf("/%s/containers/%s/logs?%s", APIVersion, id, v.Encode()) + req, err := http.NewRequest("GET", client.URL.String()+uri, nil) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + resp, err := client.HTTPClient.Do(req) + if err != nil { + return nil, err + } + return resp.Body, nil +} + +func (client *DockerClient) StartContainer(id string, config *HostConfig) error { + data, err := json.Marshal(config) + if err != nil { + return err + } + uri := fmt.Sprintf("/%s/containers/%s/start", APIVersion, id) + _, err = client.doRequest("POST", uri, data, nil) + if err != nil { + return err + } + return nil +} + +func (client *DockerClient) StopContainer(id string, timeout int) error { + uri := fmt.Sprintf("/%s/containers/%s/stop?t=%d", APIVersion, id, timeout) + _, err := client.doRequest("POST", uri, nil, nil) + if err != nil { + return err + } + return nil +} + +func (client *DockerClient) RestartContainer(id string, timeout int) error { + uri := fmt.Sprintf("/%s/containers/%s/restart?t=%d", APIVersion, id, timeout) + _, err := client.doRequest("POST", uri, nil, nil) + if err != nil { + return err + } + return nil +} + +func (client *DockerClient) KillContainer(id, signal string) error { + uri := fmt.Sprintf("/%s/containers/%s/kill?signal=%s", APIVersion, id, signal) + _, err := client.doRequest("POST", uri, nil, nil) + if err != nil { + return err + } + return nil +} + +func (client *DockerClient) StartMonitorEvents(cb Callback, ec chan error, args ...interface{}) { + atomic.StoreInt32(&client.monitorEvents, 1) + go client.getEvents(cb, ec, args...) +} + +func (client *DockerClient) getEvents(cb Callback, ec chan error, args ...interface{}) { + uri := fmt.Sprintf("%s/%s/events", client.URL.String(), APIVersion) + resp, err := client.HTTPClient.Get(uri) + if err != nil { + log.Printf("GET %s failed: %v", uri, err) + ec <- err + return + } + defer resp.Body.Close() + + dec := json.NewDecoder(resp.Body) + for atomic.LoadInt32(&client.monitorEvents) > 0 { + var event *Event + if err := dec.Decode(&event); err != nil { + log.Printf("Event decoding failed: %v", err) + ec <- err + return + } + cb(event, ec, args...) + } +} + +func (client *DockerClient) StopAllMonitorEvents() { + atomic.StoreInt32(&client.monitorEvents, 0) +} + +func (client *DockerClient) Version() (*Version, error) { + uri := fmt.Sprintf("/%s/version", APIVersion) + data, err := client.doRequest("GET", uri, nil, nil) + if err != nil { + return nil, err + } + version := &Version{} + err = json.Unmarshal(data, version) + if err != nil { + return nil, err + } + return version, nil +} + +func (client *DockerClient) PullImage(name string, auth *AuthConfig) error { + v := url.Values{} + v.Set("fromImage", name) + uri := fmt.Sprintf("/%s/images/create?%s", APIVersion, v.Encode()) + req, err := http.NewRequest("POST", client.URL.String()+uri, nil) + if auth != nil { + req.Header.Add("X-Registry-Auth", auth.encode()) + } + resp, err := client.HTTPClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + var finalObj map[string]interface{} + for decoder := json.NewDecoder(resp.Body); err == nil; err = decoder.Decode(&finalObj) { + } + if err != io.EOF { + return err + } + if err, ok := finalObj["error"]; ok { + return fmt.Errorf("%v", err) + } + return nil +} + +func (client *DockerClient) RemoveContainer(id string, force bool) error { + argForce := 0 + if force == true { + argForce = 1 + } + args := fmt.Sprintf("force=%d", argForce) + uri := fmt.Sprintf("/%s/containers/%s?%s", APIVersion, id, args) + _, err := client.doRequest("DELETE", uri, nil, nil) + return err +} + +func (client *DockerClient) ListImages() ([]*Image, error) { + uri := fmt.Sprintf("/%s/images/json", APIVersion) + data, err := client.doRequest("GET", uri, nil, nil) + if err != nil { + return nil, err + } + var images []*Image + if err := json.Unmarshal(data, &images); err != nil { + return nil, err + } + return images, nil +} + +func (client *DockerClient) RemoveImage(name string) error { + uri := fmt.Sprintf("/%s/images/%s", APIVersion, name) + _, err := client.doRequest("DELETE", uri, nil, nil) + return err +} + +func (client *DockerClient) PauseContainer(id string) error { + uri := fmt.Sprintf("/%s/containers/%s/pause", APIVersion, id) + _, err := client.doRequest("POST", uri, nil, nil) + if err != nil { + return err + } + return nil +} +func (client *DockerClient) UnpauseContainer(id string) error { + uri := fmt.Sprintf("/%s/containers/%s/unpause", APIVersion, id) + _, err := client.doRequest("POST", uri, nil, nil) + if err != nil { + return err + } + return nil +} + +func (client *DockerClient) Exec(config *ExecConfig) (string, error) { + data, err := json.Marshal(config) + if err != nil { + return "", err + } + uri := fmt.Sprintf("/containers/%s/exec", config.Container) + resp, err := client.doRequest("POST", uri, data, nil) + if err != nil { + return "", err + } + var createExecResp struct { + Id string + } + if err = json.Unmarshal(resp, &createExecResp); err != nil { + return "", err + } + uri = fmt.Sprintf("/exec/%s/start", createExecResp.Id) + resp, err = client.doRequest("POST", uri, data, nil) + if err != nil { + return "", err + } + return createExecResp.Id, nil +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go new file mode 100644 index 0000000..8d3ec2c --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go @@ -0,0 +1,153 @@ +package dockerclient + +import ( + "bytes" + "fmt" + "reflect" + "strings" + "testing" + + "github.com/docker/docker/pkg/stdcopy" +) + +func assertEqual(t *testing.T, a interface{}, b interface{}, message string) { + if a == b { + return + } + if len(message) == 0 { + message = fmt.Sprintf("%v != %v", a, b) + } + t.Fatal(message) +} + +func testDockerClient(t *testing.T) *DockerClient { + client, err := NewDockerClient(testHTTPServer.URL, nil) + if err != nil { + t.Fatal("Cannot init the docker client") + } + return client +} + +func TestInfo(t *testing.T) { + client := testDockerClient(t) + info, err := client.Info() + if err != nil { + t.Fatal("Cannot get server info") + } + assertEqual(t, info.Images, int64(1), "") + assertEqual(t, info.Containers, int64(2), "") +} + +func TestKillContainer(t *testing.T) { + client := testDockerClient(t) + if err := client.KillContainer("23132acf2ac", "5"); err != nil { + t.Fatal("cannot kill container: %s", err) + } +} + +func TestPullImage(t *testing.T) { + client := testDockerClient(t) + err := client.PullImage("busybox", nil) + if err != nil { + t.Fatal("unable to pull busybox") + } + + err = client.PullImage("haproxy", nil) + if err != nil { + t.Fatal("unable to pull haproxy") + } + + err = client.PullImage("wrongimg", nil) + if err == nil { + t.Fatal("should return error when it fails to pull wrongimg") + } +} + +func TestListContainers(t *testing.T) { + client := testDockerClient(t) + containers, err := client.ListContainers(true, false, "") + if err != nil { + t.Fatal("cannot get containers: %s", err) + } + assertEqual(t, len(containers), 1, "") + cnt := containers[0] + assertEqual(t, cnt.SizeRw, int64(0), "") +} + +func TestListContainersWithSize(t *testing.T) { + client := testDockerClient(t) + containers, err := client.ListContainers(true, true, "") + if err != nil { + t.Fatal("cannot get containers: %s", err) + } + assertEqual(t, len(containers), 1, "") + cnt := containers[0] + assertEqual(t, cnt.SizeRw, int64(123), "") +} +func TestListContainersWithFilters(t *testing.T) { + client := testDockerClient(t) + containers, err := client.ListContainers(true, true, "{'id':['332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce']}") + if err != nil { + t.Fatal("cannot get containers: %s", err) + } + assertEqual(t, len(containers), 1, "") + + containers, err = client.ListContainers(true, true, "{'id':['332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688cf']}") + if err != nil { + t.Fatal("cannot get containers: %s", err) + } + assertEqual(t, len(containers), 0, "") +} + +func TestContainerLogs(t *testing.T) { + client := testDockerClient(t) + containerId := "foobar" + logOptions := &LogOptions{ + Follow: true, + Stdout: true, + Stderr: true, + Timestamps: true, + Tail: 10, + } + logsReader, err := client.ContainerLogs(containerId, logOptions) + if err != nil { + t.Fatal("cannot read logs from server") + } + + stdoutBuffer := new(bytes.Buffer) + stderrBuffer := new(bytes.Buffer) + if _, err = stdcopy.StdCopy(stdoutBuffer, stderrBuffer, logsReader); err != nil { + t.Fatal("cannot read logs from logs reader") + } + stdoutLogs := strings.TrimSpace(stdoutBuffer.String()) + stderrLogs := strings.TrimSpace(stderrBuffer.String()) + stdoutLogLines := strings.Split(stdoutLogs, "\n") + stderrLogLines := strings.Split(stderrLogs, "\n") + if len(stdoutLogLines) != 5 { + t.Fatalf("wrong number of stdout logs: len=%d", len(stdoutLogLines)) + } + if len(stderrLogLines) != 5 { + t.Fatalf("wrong number of stderr logs: len=%d", len(stdoutLogLines)) + } + for i, line := range stdoutLogLines { + expectedSuffix := fmt.Sprintf("Z line %d", 41+2*i) + if !strings.HasSuffix(line, expectedSuffix) { + t.Fatalf("expected stdout log line \"%s\" to end with \"%s\"", line, expectedSuffix) + } + } + for i, line := range stderrLogLines { + expectedSuffix := fmt.Sprintf("Z line %d", 40+2*i) + if !strings.HasSuffix(line, expectedSuffix) { + t.Fatalf("expected stderr log line \"%s\" to end with \"%s\"", line, expectedSuffix) + } + } +} + +func TestDockerClientInterface(t *testing.T) { + iface := reflect.TypeOf((*Client)(nil)).Elem() + test := testDockerClient(t) + + if !reflect.TypeOf(test).Implements(iface) { + t.Fatalf("DockerClient does not implement the Client interface") + } +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/engine_mock_test.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/engine_mock_test.go new file mode 100644 index 0000000..4df2276 --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/engine_mock_test.go @@ -0,0 +1,210 @@ +package dockerclient + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/http/httptest" + "strconv" + "time" + + "github.com/docker/docker/pkg/jsonlog" + "github.com/docker/docker/pkg/stdcopy" + "github.com/docker/docker/pkg/timeutils" + "github.com/docker/docker/utils" + "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/gorilla/mux" +) + +var ( + testHTTPServer *httptest.Server +) + +func init() { + r := mux.NewRouter() + baseURL := "/" + APIVersion + r.HandleFunc(baseURL+"/info", handlerGetInfo).Methods("GET") + r.HandleFunc(baseURL+"/containers/json", handlerGetContainers).Methods("GET") + r.HandleFunc(baseURL+"/containers/{id}/logs", handleContainerLogs).Methods("GET") + r.HandleFunc(baseURL+"/containers/{id}/kill", handleContainerKill).Methods("POST") + r.HandleFunc(baseURL+"/images/create", handleImagePull).Methods("POST") + testHTTPServer = httptest.NewServer(handlerAccessLog(r)) +} + +func handlerAccessLog(handler http.Handler) http.Handler { + logHandler := func(w http.ResponseWriter, r *http.Request) { + log.Printf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL) + handler.ServeHTTP(w, r) + } + return http.HandlerFunc(logHandler) +} + +func handleContainerKill(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "{%q:%q", "Id", "421373210afd132") +} + +func handleImagePull(w http.ResponseWriter, r *http.Request) { + imageName := r.URL.Query()["fromImage"][0] + responses := []map[string]interface{}{{ + "status": fmt.Sprintf("Pulling repository mydockerregistry/%s", imageName), + }} + switch imageName { + case "busybox": + responses = append(responses, map[string]interface{}{ + "status": "Status: Image is up to date for mydockerregistry/busybox", + }) + case "haproxy": + fmt.Fprintf(w, haproxyPullOutput) + return + default: + errorMsg := fmt.Sprintf("Error: image %s not found", imageName) + responses = append(responses, map[string]interface{}{ + "errorDetail": map[string]interface{}{ + "message": errorMsg, + }, + "error": errorMsg, + }) + } + for _, response := range responses { + json.NewEncoder(w).Encode(response) + } +} + +func handleContainerLogs(w http.ResponseWriter, r *http.Request) { + var outStream, errStream io.Writer + outStream = utils.NewWriteFlusher(w) + + // not sure how to test follow + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), 500) + } + stdout, stderr := getBoolValue(r.Form.Get("stdout")), getBoolValue(r.Form.Get("stderr")) + if stderr { + errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) + } + if stdout { + outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) + } + var i int + if tail, err := strconv.Atoi(r.Form.Get("tail")); err == nil && tail > 0 { + i = 50 - tail + if i < 0 { + i = 0 + } + } + for ; i < 50; i++ { + line := fmt.Sprintf("line %d", i) + if getBoolValue(r.Form.Get("timestamps")) { + l := &jsonlog.JSONLog{Log: line, Created: time.Now()} + line = fmt.Sprintf("%s %s", l.Created.Format(timeutils.RFC3339NanoFixed), line) + } + if i%2 == 0 && stderr { + fmt.Fprintln(errStream, line) + } else if i%2 == 1 && stdout { + fmt.Fprintln(outStream, line) + } + } +} + +func getBoolValue(boolString string) bool { + switch boolString { + case "1": + return true + case "True": + return true + case "true": + return true + default: + return false + } +} + +func writeHeaders(w http.ResponseWriter, code int, jobName string) { + h := w.Header() + h.Add("Content-Type", "application/json") + if jobName != "" { + h.Add("Job-Name", jobName) + } + w.WriteHeader(code) +} + +func handlerGetInfo(w http.ResponseWriter, r *http.Request) { + writeHeaders(w, 200, "info") + body := `{ + "Containers": 2, + "Debug": 1, + "Driver": "aufs", + "DriverStatus": [["Root Dir", "/mnt/sda1/var/lib/docker/aufs"], + ["Dirs", "0"]], + "ExecutionDriver": "native-0.2", + "IPv4Forwarding": 1, + "Images": 1, + "IndexServerAddress": "https://index.docker.io/v1/", + "InitPath": "/usr/local/bin/docker", + "InitSha1": "", + "KernelVersion": "3.16.4-tinycore64", + "MemoryLimit": 1, + "NEventsListener": 0, + "NFd": 10, + "NGoroutines": 11, + "OperatingSystem": "Boot2Docker 1.3.1 (TCL 5.4); master : a083df4 - Thu Jan 01 00:00:00 UTC 1970", + "SwapLimit": 1}` + w.Write([]byte(body)) +} + +func handlerGetContainers(w http.ResponseWriter, r *http.Request) { + writeHeaders(w, 200, "containers") + body := `[ + { + "Status": "Up 39 seconds", + "Ports": [ + { + "Type": "tcp", + "PublicPort": 49163, + "PrivatePort": 8080, + "IP": "0.0.0.0" + } + ], + "Names": [ + "/trusting_heisenberg" + ], + "Image": "foo:latest", + "Id": "332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce", + "Created": 1415720105, + "Command": "/bin/go-run" + } + ]` + if v, ok := r.URL.Query()["size"]; ok { + if v[0] == "1" { + body = `[ + { + "Status": "Up 39 seconds", + "Ports": [ + { + "Type": "tcp", + "PublicPort": 49163, + "PrivatePort": 8080, + "IP": "0.0.0.0" + } + ], + "Names": [ + "/trusting_heisenberg" + ], + "Image": "foo:latest", + "Id": "332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce", + "Created": 1415720105, + "SizeRootFs": 12345, + "SizeRW": 123, + "Command": "/bin/go-run" + } + ]` + } + } + if v, ok := r.URL.Query()["filters"]; ok { + if v[0] != "{'id':['332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce']}" { + body = "[]" + } + } + w.Write([]byte(body)) +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/example_responses.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/example_responses.go new file mode 100644 index 0000000..9f683f1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/example_responses.go @@ -0,0 +1,11 @@ +package dockerclient + +var haproxyPullOutput = `{"status":"The image you are pulling has been verified","id":"haproxy:1"} +{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"The image you are pulling has been verified","id":"haproxy:1.4"} +{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"63a1b9929e14"}{"status":"Already exists","progressDetail":{},"id":"af43bf7d176e"}{"status":"Already exists","progressDetail":{},"id":"851aac2d69aa"}{"status":"Already exists","progressDetail":{},"id":"345053a92c95"}{"status":"Already exists","progressDetail":{},"id":"b41231d429c9"}{"status":"The image you are pulling has been verified","id":"haproxy:1.4.25"} +{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"63a1b9929e14"}{"status":"Already exists","progressDetail":{},"id":"af43bf7d176e"}{"status":"Already exists","progressDetail":{},"id":"851aac2d69aa"}{"status":"Already exists","progressDetail":{},"id":"345053a92c95"}{"status":"Already exists","progressDetail":{},"id":"b41231d429c9"}{"status":"The image you are pulling has been verified","id":"haproxy:1.5"} +{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"The image you are pulling has been verified","id":"haproxy:1.5.10"} +{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"The image you are pulling has been verified","id":"haproxy:1.5.9"} +{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"3d894e6f7e63"}{"status":"Already exists","progressDetail":{},"id":"4d949c40bc77"}{"status":"Already exists","progressDetail":{},"id":"55e031889365"}{"status":"Already exists","progressDetail":{},"id":"c7aa675e1876"}{"status":"The image you are pulling has been verified","id":"haproxy:latest"} +{"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"Status: Image is up to date for haproxy"} +` diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/examples/events.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/examples/events.go new file mode 100644 index 0000000..7c12ab3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/examples/events.go @@ -0,0 +1,32 @@ +package main + +import ( + "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/samalba/dockerclient" + "log" + "os" + "os/signal" + "syscall" +) + +func eventCallback(e *dockerclient.Event, ec chan error, args ...interface{}) { + log.Println(e) +} + +func waitForInterrupt() { + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) + for _ = range sigChan { + os.Exit(0) + } +} + +func main() { + docker, err := dockerclient.NewDockerClient(os.Getenv("DOCKER_HOST"), nil) + if err != nil { + log.Fatal(err) + } + + docker.StartMonitorEvents(eventCallback, nil) + + waitForInterrupt() +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go new file mode 100644 index 0000000..6a270f1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go @@ -0,0 +1,29 @@ +package dockerclient + +import ( + "io" +) + +type Callback func(*Event, chan error, ...interface{}) + +type Client interface { + Info() (*Info, error) + ListContainers(all, size bool, filters string) ([]Container, error) + InspectContainer(id string) (*ContainerInfo, error) + CreateContainer(config *ContainerConfig, name string) (string, error) + ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error) + Exec(config *ExecConfig) (string, error) + StartContainer(id string, config *HostConfig) error + StopContainer(id string, timeout int) error + RestartContainer(id string, timeout int) error + KillContainer(id, signal string) error + StartMonitorEvents(cb Callback, ec chan error, args ...interface{}) + StopAllMonitorEvents() + Version() (*Version, error) + PullImage(name string, auth *AuthConfig) error + RemoveContainer(id string, force bool) error + ListImages() ([]*Image, error) + RemoveImage(name string) error + PauseContainer(name string) error + UnpauseContainer(name string) error +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go new file mode 100644 index 0000000..9c5f4c0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go @@ -0,0 +1,109 @@ +package mockclient + +import ( + "io" + + "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/samalba/dockerclient" + "github.com/stretchr/testify/mock" +) + +type MockClient struct { + mock.Mock +} + +func NewMockClient() *MockClient { + return &MockClient{} +} + +func (client *MockClient) Info() (*dockerclient.Info, error) { + args := client.Mock.Called() + return args.Get(0).(*dockerclient.Info), args.Error(1) +} + +func (client *MockClient) ListContainers(all bool, size bool, filters string) ([]dockerclient.Container, error) { + args := client.Mock.Called(all, size, filters) + return args.Get(0).([]dockerclient.Container), args.Error(1) +} + +func (client *MockClient) InspectContainer(id string) (*dockerclient.ContainerInfo, error) { + args := client.Mock.Called(id) + return args.Get(0).(*dockerclient.ContainerInfo), args.Error(1) +} + +func (client *MockClient) CreateContainer(config *dockerclient.ContainerConfig, name string) (string, error) { + args := client.Mock.Called(config, name) + return args.String(0), args.Error(1) +} + +func (client *MockClient) ContainerLogs(id string, options *dockerclient.LogOptions) (io.ReadCloser, error) { + args := client.Mock.Called(id, options) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +func (client *MockClient) StartContainer(id string, config *dockerclient.HostConfig) error { + args := client.Mock.Called(id, config) + return args.Error(0) +} + +func (client *MockClient) StopContainer(id string, timeout int) error { + args := client.Mock.Called(id, timeout) + return args.Error(0) +} + +func (client *MockClient) RestartContainer(id string, timeout int) error { + args := client.Mock.Called(id, timeout) + return args.Error(0) +} + +func (client *MockClient) KillContainer(id, signal string) error { + args := client.Mock.Called(id, signal) + return args.Error(0) +} + +func (client *MockClient) StartMonitorEvents(cb dockerclient.Callback, ec chan error, args ...interface{}) { + client.Mock.Called(cb, ec, args) +} + +func (client *MockClient) StopAllMonitorEvents() { + client.Mock.Called() +} + +func (client *MockClient) Version() (*dockerclient.Version, error) { + args := client.Mock.Called() + return args.Get(0).(*dockerclient.Version), args.Error(1) +} + +func (client *MockClient) PullImage(name string, auth *dockerclient.AuthConfig) error { + args := client.Mock.Called(name, auth) + return args.Error(0) +} + +func (client *MockClient) RemoveContainer(id string, force bool) error { + args := client.Mock.Called(id, force) + return args.Error(0) +} + +func (client *MockClient) ListImages() ([]*dockerclient.Image, error) { + args := client.Mock.Called() + return args.Get(0).([]*dockerclient.Image), args.Error(1) +} + +func (client *MockClient) RemoveImage(name string) error { + args := client.Mock.Called(name) + return args.Error(0) +} + +func (client *MockClient) PauseContainer(name string) error { + args := client.Mock.Called(name) + return args.Error(0) +} + +func (client *MockClient) UnpauseContainer(name string) error { + args := client.Mock.Called(name) + return args.Error(0) +} + +func (client *MockClient) Exec(config *dockerclient.ExecConfig) (string, error) { + args := client.Mock.Called(config) + return args.String(0), args.Error(1) +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock_test.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock_test.go new file mode 100644 index 0000000..d72eda0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock_test.go @@ -0,0 +1,32 @@ +package mockclient + +import ( + "reflect" + "testing" + + "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/samalba/dockerclient" +) + +func TestMock(t *testing.T) { + mock := NewMockClient() + mock.On("Version").Return(&dockerclient.Version{Version: "foo"}, nil).Once() + + v, err := mock.Version() + if err != nil { + t.Fatal(err) + } + if v.Version != "foo" { + t.Fatal(v) + } + + mock.Mock.AssertExpectations(t) +} + +func TestMockInterface(t *testing.T) { + iface := reflect.TypeOf((*dockerclient.Client)(nil)).Elem() + mock := NewMockClient() + + if !reflect.TypeOf(mock).Implements(iface) { + t.Fatalf("Mock does not implement the Client interface") + } +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go new file mode 100644 index 0000000..6504e8f --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go @@ -0,0 +1,168 @@ +package dockerclient + +import "time" + +type ContainerConfig struct { + Hostname string + Domainname string + User string + Memory int64 + MemorySwap int64 + CpuShares int64 + Cpuset string + AttachStdin bool + AttachStdout bool + AttachStderr bool + PortSpecs []string + ExposedPorts map[string]struct{} + Tty bool + OpenStdin bool + StdinOnce bool + Env []string + Cmd []string + Image string + Volumes map[string]struct{} + WorkingDir string + Entrypoint []string + NetworkDisabled bool + OnBuild []string + + // This is used only by the create command + HostConfig HostConfig +} + +type HostConfig struct { + Binds []string + ContainerIDFile string + LxcConf []map[string]string + Privileged bool + PortBindings map[string][]PortBinding + Links []string + PublishAllPorts bool + Dns []string + DnsSearch []string + VolumesFrom []string + NetworkMode string + RestartPolicy RestartPolicy +} + +type ExecConfig struct { + AttachStdin bool + AttachStdout bool + AttachStderr bool + Tty bool + Cmd []string + Container string + Detach bool +} + +type LogOptions struct { + Follow bool + Stdout bool + Stderr bool + Timestamps bool + Tail int64 +} + +type RestartPolicy struct { + Name string + MaximumRetryCount int64 +} + +type PortBinding struct { + HostIp string + HostPort string +} + +type ContainerInfo struct { + Id string + Created string + Path string + Name string + Args []string + ExecIDs []string + Config *ContainerConfig + State struct { + Running bool + Paused bool + Restarting bool + Pid int + ExitCode int + StartedAt time.Time + FinishedAt time.Time + Ghost bool + } + Image string + NetworkSettings struct { + IpAddress string + IpPrefixLen int + Gateway string + Bridge string + Ports map[string][]PortBinding + } + SysInitPath string + ResolvConfPath string + Volumes map[string]string + HostConfig *HostConfig +} + +type Port struct { + IP string + PrivatePort int + PublicPort int + Type string +} + +type Container struct { + Id string + Names []string + Image string + Command string + Created int64 + Status string + Ports []Port + SizeRw int64 + SizeRootFs int64 +} + +type Event struct { + Id string + Status string + From string + Time int64 +} + +type Version struct { + Version string + GitCommit string + GoVersion string +} + +type RespContainersCreate struct { + Id string + Warnings []string +} + +type Image struct { + Created int64 + Id string + ParentId string + RepoTags []string + Size int64 + VirtualSize int64 +} + +type Info struct { + ID string + Containers int64 + Driver string + DriverStatus [][]string + ExecutionDriver string + Images int64 + KernelVersion string + OperatingSystem string + NCPU int64 + MemTotal int64 + Name string + Labels []string +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/utils.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/utils.go new file mode 100644 index 0000000..806f1b3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/utils.go @@ -0,0 +1,33 @@ +package dockerclient + +import ( + "crypto/tls" + "net" + "net/http" + "net/url" + "time" +) + +func newHTTPClient(u *url.URL, tlsConfig *tls.Config, timeout time.Duration) *http.Client { + httpTransport := &http.Transport{ + TLSClientConfig: tlsConfig, + } + + switch u.Scheme { + default: + httpTransport.Dial = func(proto, addr string) (net.Conn, error) { + return net.DialTimeout(proto, addr, timeout) + } + case "unix": + socketPath := u.Path + unixDial := func(proto, addr string) (net.Conn, error) { + return net.DialTimeout("unix", socketPath, timeout) + } + httpTransport.Dial = unixDial + // Override the main URL object so the HTTP lib won't complain + u.Scheme = "http" + u.Host = "unix.sock" + u.Path = "" + } + return &http.Client{Transport: httpTransport} +} diff --git a/Godeps/_workspace/src/github.com/vishvananda/netlink/link_test.go b/Godeps/_workspace/src/github.com/vishvananda/netlink/link_test.go index 232ad03..ecfad71 100644 --- a/Godeps/_workspace/src/github.com/vishvananda/netlink/link_test.go +++ b/Godeps/_workspace/src/github.com/vishvananda/netlink/link_test.go @@ -3,7 +3,7 @@ package netlink import ( "testing" - "github.com/vishvananda/netns" + "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/vishvananda/netns" ) const testTxQLen uint32 = 100 diff --git a/Godeps/_workspace/src/github.com/vishvananda/netlink/netlink_test.go b/Godeps/_workspace/src/github.com/vishvananda/netlink/netlink_test.go index 718448b..9edace4 100644 --- a/Godeps/_workspace/src/github.com/vishvananda/netlink/netlink_test.go +++ b/Godeps/_workspace/src/github.com/vishvananda/netlink/netlink_test.go @@ -1,7 +1,7 @@ package netlink import ( - "github.com/vishvananda/netns" + "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/vishvananda/netns" "log" "os" "runtime" diff --git a/Godeps/_workspace/src/github.com/vishvananda/netns/LICENSE b/Godeps/_workspace/src/github.com/vishvananda/netns/LICENSE new file mode 100644 index 0000000..9f64db8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vishvananda/netns/LICENSE @@ -0,0 +1,192 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2014 Vishvananda Ishaya. + Copyright 2014 Docker, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Godeps/_workspace/src/github.com/vishvananda/netns/README.md b/Godeps/_workspace/src/github.com/vishvananda/netns/README.md new file mode 100644 index 0000000..24a4003 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vishvananda/netns/README.md @@ -0,0 +1,49 @@ +# netns - network namespaces in go # + +The netns package provides an ultra-simple interface for handling +network namespaces in go. Changing namespaces requires elevated +privileges, so in most cases this code needs to be run as root. + +## Local Build and Test ## + +You can use go get command: + + go get github.com/vishvananda/netns + +Testing (requires root): + + sudo -E go test github.com/vishvananda/netns + +## Example ## + +```go +package main + +import ( + "net" + "runtime" + "github.com/vishvananada/netns" +) + +func main() { + // Lock the OS Thread so we don't accidentally switch namespaces + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + // Save the current network namespace + origns, _ := netns.Get() + defer origns.Close() + + // Create a new network namespace + newns, _ := netns.New() + defer newns.Close() + + // Do something with tne network namespace + ifaces, _ := net.Interfaces() + fmt.Printf("Interfaces: %v\n", ifaces) + + // Switch back to the original namespace + netns.Set(origns) +} + +``` diff --git a/Godeps/_workspace/src/github.com/vishvananda/netns/netns.go b/Godeps/_workspace/src/github.com/vishvananda/netns/netns.go new file mode 100644 index 0000000..3878da3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vishvananda/netns/netns.go @@ -0,0 +1,66 @@ +// Package netns allows ultra-simple network namespace handling. NsHandles +// can be retrieved and set. Note that the current namespace is thread +// local so actions that set and reset namespaces should use LockOSThread +// to make sure the namespace doesn't change due to a goroutine switch. +// It is best to close NsHandles when you are done with them. This can be +// accomplished via a `defer ns.Close()` on the handle. Changing namespaces +// requires elevated privileges, so in most cases this code needs to be run +// as root. +package netns + +import ( + "fmt" + "syscall" +) +// NsHandle is a handle to a network namespace. It can be cast directly +// to an int and used as a file descriptor. +type NsHandle int + +// Equal determines if two network handles refer to the same network +// namespace. This is done by comparing the device and inode that the +// file descripors point to. +func (ns NsHandle) Equal(other NsHandle) bool { + if ns == other { + return true + } + var s1, s2 syscall.Stat_t + if err := syscall.Fstat(int(ns), &s1); err != nil { + return false + } + if err := syscall.Fstat(int(other), &s2); err != nil { + return false + } + return (s1.Dev == s2.Dev) && (s1.Ino == s2.Ino) +} + +// String shows the file descriptor number and its dev and inode. +func (ns NsHandle) String() string { + var s syscall.Stat_t + if ns == -1 { + return "NS(None)" + } + if err := syscall.Fstat(int(ns), &s); err != nil { + return fmt.Sprintf("NS(%d: unknown)", ns) + } + return fmt.Sprintf("NS(%d: %d, %d)", ns, s.Dev, s.Ino) +} + +// IsOpen returns true if Close() has not been called. +func (ns NsHandle) IsOpen() bool { + return ns != -1 +} + +// Close closes the NsHandle and resets its file descriptor to -1. +// It is not safe to use an NsHandle after Close() is called. +func (ns *NsHandle) Close() error { + if err := syscall.Close(int(*ns)); err != nil { + return err + } + (*ns) = -1 + return nil +} + +// Get an empty (closed) NsHandle +func None() NsHandle { + return NsHandle(-1) +} diff --git a/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux.go b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux.go new file mode 100644 index 0000000..b6049d2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux.go @@ -0,0 +1,196 @@ +package netns + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" +) + +const ( + // These constants belong in the syscall library but have not been + // added yet. + CLONE_NEWUTS = 0x04000000 /* New utsname group? */ + CLONE_NEWIPC = 0x08000000 /* New ipcs */ + CLONE_NEWUSER = 0x10000000 /* New user namespace */ + CLONE_NEWPID = 0x20000000 /* New pid namespace */ + CLONE_NEWNET = 0x40000000 /* New network namespace */ + CLONE_IO = 0x80000000 /* Get io context */ +) + +// Setns sets namespace using syscall. Note that this should be a method +// in syscall but it has not been added. +func Setns(ns NsHandle, nstype int) (err error) { + _, _, e1 := syscall.Syscall(SYS_SETNS, uintptr(ns), uintptr(nstype), 0) + if e1 != 0 { + err = e1 + } + return +} + +// Set sets the current network namespace to the namespace represented +// by NsHandle. +func Set(ns NsHandle) (err error) { + return Setns(ns, CLONE_NEWNET) +} + +// New creates a new network namespace and returns a handle to it. +func New() (ns NsHandle, err error) { + if err := syscall.Unshare(CLONE_NEWNET); err != nil { + return -1, err + } + return Get() +} + +// Get gets a handle to the current threads network namespace. +func Get() (NsHandle, error) { + return GetFromPid(os.Getpid()) +} + +// GetFromName gets a handle to a named network namespace such as one +// created by `ip netns add`. +func GetFromName(name string) (NsHandle, error) { + fd, err := syscall.Open(fmt.Sprintf("/var/run/netns/%s", name), syscall.O_RDONLY, 0) + if err != nil { + return -1, err + } + return NsHandle(fd), nil +} + +// GetFromName gets a handle to the network namespace of a given pid. +func GetFromPid(pid int) (NsHandle, error) { + fd, err := syscall.Open(fmt.Sprintf("/proc/%d/ns/net", pid), syscall.O_RDONLY, 0) + if err != nil { + return -1, err + } + return NsHandle(fd), nil +} + +// GetFromName gets a handle to the network namespace of a docker container. +// Id is prefixed matched against the running docker containers, so a short +// identifier can be used as long as it isn't ambiguous. +func GetFromDocker(id string) (NsHandle, error) { + pid, err := getPidForContainer(id) + if err != nil { + return -1, err + } + return GetFromPid(pid) +} + +// borrowed from docker/utils/utils.go +func findCgroupMountpoint(cgroupType string) (string, error) { + output, err := ioutil.ReadFile("/proc/mounts") + if err != nil { + return "", err + } + + // /proc/mounts has 6 fields per line, one mount per line, e.g. + // cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0 + for _, line := range strings.Split(string(output), "\n") { + parts := strings.Split(line, " ") + if len(parts) == 6 && parts[2] == "cgroup" { + for _, opt := range strings.Split(parts[3], ",") { + if opt == cgroupType { + return parts[1], nil + } + } + } + } + + return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType) +} + +// Returns the relative path to the cgroup docker is running in. +// borrowed from docker/utils/utils.go +// modified to get the docker pid instead of using /proc/self +func getThisCgroup(cgroupType string) (string, error) { + dockerpid, err := ioutil.ReadFile("/var/run/docker.pid") + if err != nil { + return "", err + } + result := strings.Split(string(dockerpid), "\n") + if len(result) == 0 || len(result[0]) == 0 { + return "", fmt.Errorf("docker pid not found in /var/run/docker.pid") + } + pid, err := strconv.Atoi(result[0]) + + output, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cgroup", pid)) + if err != nil { + return "", err + } + for _, line := range strings.Split(string(output), "\n") { + parts := strings.Split(line, ":") + // any type used by docker should work + if parts[1] == cgroupType { + return parts[2], nil + } + } + return "", fmt.Errorf("cgroup '%s' not found in /proc/%d/cgroup", cgroupType, pid) +} + +// Returns the first pid in a container. +// borrowed from docker/utils/utils.go +// modified to only return the first pid +// modified to glob with id +// modified to search for newer docker containers +func getPidForContainer(id string) (int, error) { + pid := 0 + + // memory is chosen randomly, any cgroup used by docker works + cgroupType := "memory" + + cgroupRoot, err := findCgroupMountpoint(cgroupType) + if err != nil { + return pid, err + } + + cgroupThis, err := getThisCgroup(cgroupType) + if err != nil { + return pid, err + } + + id += "*" + + attempts := []string{ + filepath.Join(cgroupRoot, cgroupThis, id, "tasks"), + // With more recent lxc versions use, cgroup will be in lxc/ + filepath.Join(cgroupRoot, cgroupThis, "lxc", id, "tasks"), + // With more recent dockee, cgroup will be in docker/ + filepath.Join(cgroupRoot, cgroupThis, "docker", id, "tasks"), + } + + var filename string + for _, attempt := range attempts { + filenames, _ := filepath.Glob(attempt) + if len(filenames) > 1 { + return pid, fmt.Errorf("Ambiguous id supplied: %v", filenames) + } else if len(filenames) == 1 { + filename = filenames[0] + break; + } + } + + if filename == "" { + return pid, fmt.Errorf("Unable to find container: %v", id[:len(id)-1]) + } + + output, err := ioutil.ReadFile(filename) + if err != nil { + return pid, err + } + + result := strings.Split(string(output), "\n") + if len(result) == 0 || len(result[0]) == 0 { + return pid, fmt.Errorf("No pid found for container") + } + + pid, err = strconv.Atoi(result[0]) + if err != nil { + return pid, fmt.Errorf("Invalid pid '%s': %s", result[0], err) + } + + return pid, nil +} diff --git a/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_386.go b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_386.go new file mode 100644 index 0000000..0a6fe49 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_386.go @@ -0,0 +1,5 @@ +package netns + +const ( + SYS_SETNS = 346 +) diff --git a/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_amd.go b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_amd.go new file mode 100644 index 0000000..bbf3f4d --- /dev/null +++ b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_amd.go @@ -0,0 +1,5 @@ +package netns + +const ( + SYS_SETNS = 308 +) diff --git a/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_arm.go b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_arm.go new file mode 100644 index 0000000..e35cb07 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_linux_arm.go @@ -0,0 +1,5 @@ +package netns + +const ( + SYS_SETNS = 374 +) diff --git a/Godeps/_workspace/src/github.com/vishvananda/netns/netns_test.go b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_test.go new file mode 100644 index 0000000..b685b9d --- /dev/null +++ b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_test.go @@ -0,0 +1,44 @@ +package netns + +import ( + "runtime" + "testing" +) + +func TestGetNewSetDelete(t *testing.T) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + origns, err := Get() + if err != nil { + t.Fatal(err) + } + newns, err := New() + if err != nil { + t.Fatal(err) + } + if origns.Equal(newns) { + t.Fatal("New ns failed") + } + if err := Set(origns); err != nil { + t.Fatal(err) + } + newns.Close() + if newns.IsOpen() { + t.Fatal("newns still open after close", newns) + } + ns, err := Get() + if err != nil { + t.Fatal(err) + } + if !ns.Equal(origns) { + t.Fatal("Reset ns failed", origns, newns, ns) + } +} + +func TestNone(t *testing.T) { + ns := None() + if ns.IsOpen() { + t.Fatal("None ns is open", ns) + } +} diff --git a/Godeps/_workspace/src/github.com/vishvananda/netns/netns_unspecified.go b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_unspecified.go new file mode 100644 index 0000000..42a804f --- /dev/null +++ b/Godeps/_workspace/src/github.com/vishvananda/netns/netns_unspecified.go @@ -0,0 +1,35 @@ +// +build !linux + +package netns + +import ( + "errors" +) + +var ( + ErrNotImplemented = errors.New("not implemented") +) + +func Set(ns Namespace) (err error) { + return ErrNotImplemented +} + +func New() (ns Namespace, err error) { + return -1, ErrNotImplemented +} + +func Get() (Namespace, error) { + return -1, ErrNotImplemented +} + +func GetFromName(name string) (Namespace, error) { + return -1, ErrNotImplemented +} + +func GetFromPid(pid int) (Namespace, error) { + return -1, ErrNotImplemented +} + +func GetFromDocker(id string) (Namespace, error) { + return -1, ErrNotImplemented +} diff --git a/Vagrantfile b/Vagrantfile index be0e672..f2f3ecc 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -15,7 +15,10 @@ export DEBIAN_FRONTEND=noninteractive # -qq is pointless, it doesn't work :S apt-get update > /dev/null echo ====> Installing Packages -apt-get install -qq -y --no-install-recommends docker.io openvswitch-switch unzip +apt-get install -qq -y --no-install-recommends openvswitch-switch unzip +curl -sSL https://get.docker.com/ubuntu/ | sh > /dev/null +mkdir -p /etc/socketplane +ln -s /vagrant/adapters.yml /etc/socketplane/adapters.yml ln -s /vagrant/scripts/socketplane.sh /usr/bin/socketplane cd /usr/bin wget --quiet https://dl.bintray.com/mitchellh/consul/0.4.1_linux_amd64.zip diff --git a/adapters.yml b/adapters.yml new file mode 100644 index 0000000..589c273 --- /dev/null +++ b/adapters.yml @@ -0,0 +1,8 @@ +version: 1 +endpoints: + "POST /*/containers/create": + pre: [socketplane] + "POST /*/containers/*/start": + post: [socketplane] +adapters: + socketplane: http://localhost:6675/adapter diff --git a/daemon/api.go b/daemon/api.go index 6bd63a4..059bf3a 100644 --- a/daemon/api.go +++ b/daemon/api.go @@ -6,9 +6,13 @@ import ( "net" "net/http" "net/url" + "regexp" + "strconv" + "strings" log "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/Sirupsen/logrus" "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/gorilla/mux" + "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/samalba/dockerclient" ) const API_VERSION string = "/v0.1" @@ -34,6 +38,39 @@ type Connection struct { ConnectionDetails OvsConnection `json:"connection_details"` } +type adapterRequest struct { + PowerstripProtocolVersion int + Type string + ClientRequest struct { + Method string + Request string + Body string + } + ServerResponse struct { + Body string + Code int + ContentType string + } +} + +type adapterPreResponse struct { + PowerstripProtocolVersion int + ModifiedClientRequest struct { + Method string + Request string + Body string + } +} + +type adapterPostResponse struct { + PowerstripProtocolVersion int + ModifiedServerResponse struct { + Body string + Code int + ContentType string + } +} + type apiError struct { Code int Message string @@ -79,6 +116,7 @@ func createRouter(d *Daemon) *mux.Router { "/cluster/bind": clusterBind, "/cluster/join": clusterJoin, "/cluster/leave": clusterLeave, + "/adapter": psAdapter, }, "DELETE": { "/connections/{id:.*}": deleteConnection, @@ -90,12 +128,107 @@ func createRouter(d *Daemon) *mux.Router { for route, fct := range routes { handler := appHandler{d, fct} r.Path(API_VERSION + route).Methods(method).Handler(handler) + if route == "/adapter" { + r.Path(route).Methods(method).Handler(handler) + } } } return r } +func psAdapterPreHook(d *Daemon, reqParams adapterRequest) *adapterPreResponse { + if reqParams.ClientRequest.Body != "" { + jsonBody := &dockerclient.ContainerConfig{} + err := json.Unmarshal([]byte(reqParams.ClientRequest.Body), &jsonBody) + if err != nil { + fmt.Println("Body JSON unmarsall failed", err) + } + + jsonBody.HostConfig.NetworkMode = "none" + + preResp := &adapterPreResponse{} + preResp.PowerstripProtocolVersion = reqParams.PowerstripProtocolVersion + preResp.ModifiedClientRequest.Method = reqParams.ClientRequest.Method + preResp.ModifiedClientRequest.Request = reqParams.ClientRequest.Request + + body, _ := json.Marshal(jsonBody) + preResp.ModifiedClientRequest.Body = string(body) + + return preResp + } + return nil +} + +func psAdapterPostHook(d *Daemon, reqParams adapterRequest) *adapterPostResponse { + if reqParams.ClientRequest.Request != "" { + // start api looks like this //containers//start + s := regexp.MustCompile("/").Split(reqParams.ClientRequest.Request, 5) + cid := s[3] + + docker, _ := dockerclient.NewDockerClient( + "unix:///var/run/docker.sock", nil) + info, err := docker.InspectContainer(cid) + if err != nil { + fmt.Println("InspectContainer failed", err) + } + + cfg := &Connection{} + + cfg.ContainerID = string(cid) + cfg.ContainerName = info.Name + cfg.ContainerPID = strconv.Itoa(info.State.Pid) + cfg.Network = DefaultNetworkName + for _, env := range info.Config.Env { + val := regexp.MustCompile("=").Split(env, 3) + if val[0] == "SP_NETWORK" { + cfg.Network = strings.Trim(val[1], " ") + } + } + + context := &ConnectionContext{ + ConnectionAdd, + cfg, + make(chan *Connection), + } + d.cC <- context + + <-context.Result + + postResp := &adapterPostResponse{} + postResp.PowerstripProtocolVersion = reqParams.PowerstripProtocolVersion + postResp.ModifiedServerResponse.ContentType = "application/json" + postResp.ModifiedServerResponse.Body = reqParams.ServerResponse.Body + postResp.ModifiedServerResponse.Code = reqParams.ServerResponse.Code + + return postResp + } + + return nil +} + +func psAdapter(d *Daemon, w http.ResponseWriter, r *http.Request) *apiError { + var reqParams adapterRequest + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&reqParams) + if err != nil { + fmt.Println("Error decodeing JSON", err) + //return &apiError{http.StatusInternalServerError, err.Error()} + } + + var data []byte + switch reqParams.Type { + case "pre-hook": + data, _ = json.Marshal(psAdapterPreHook(d, reqParams)) + case "post-hook": + data, _ = json.Marshal(psAdapterPostHook(d, reqParams)) + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Write(data) + return nil +} + func getConfiguration(d *Daemon, w http.ResponseWriter, r *http.Request) *apiError { data, _ := json.Marshal(d.Configuration) w.Header().Set("Content-Type", "application/json; charset=utf-8") diff --git a/daemon/bridge.go b/daemon/bridge.go index c44fced..2ec925a 100644 --- a/daemon/bridge.go +++ b/daemon/bridge.go @@ -7,13 +7,17 @@ import ( "fmt" "io" "net" + "os" "os/exec" + "path/filepath" + "runtime" "strconv" "strings" "time" log "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/Sirupsen/logrus" "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/socketplane/libovsdb" + "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/vishvananda/netns" "github.com/socketplane/socketplane/ipam" ) @@ -259,13 +263,41 @@ func AddConnection(nspid int, networkName string) (ovsConnection OvsConnection, if err = InterfaceUp(portName); err != nil { return } - if err = SetInterfaceInNamespacePid(portName, nspid); err != nil { + + if err = os.Symlink(filepath.Join(os.Getenv("PROCFS"), strconv.Itoa(nspid), "ns/net"), + filepath.Join("/var/run/netns", strconv.Itoa(nspid))); err != nil { + return + } + + // Lock the OS Thread so we don't accidentally switch namespaces + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + origns, err := netns.Get() + if err != nil { + return + } + defer origns.Close() + + targetns, err := netns.GetFromName(strconv.Itoa(nspid)) + if err != nil { return } + defer targetns.Close() + + if err = SetInterfaceInNamespaceFd(portName, uintptr(int(targetns))); err != nil { + return + } + + if err := netns.Set(targetns); err != nil { + return + } + defer netns.Set(origns) if err = InterfaceDown(portName); err != nil { return } + // TODO : Find a way to change the interface name to defaultDevice (eth0). // Currently using the Randomly created OVS port as is. // refer to veth.go where one end of the veth pair is renamed to eth0 @@ -273,9 +305,10 @@ func AddConnection(nspid int, networkName string) (ovsConnection OvsConnection, return } - if err = SetInterfaceIp(portName, ip.String()); err != nil { + if err = SetInterfaceIp(portName, ip.String()+subnetPrefix); err != nil { return } + if err = SetInterfaceMac(portName, generateMacAddr(ip).String()); err != nil { return } @@ -283,9 +316,11 @@ func AddConnection(nspid int, networkName string) (ovsConnection OvsConnection, if err = InterfaceUp(portName); err != nil { return } + if err = SetDefaultGateway(bridgeNetwork.Gateway, portName); err != nil { return } + return ovsConnection, nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index 9cd1834..973483c 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -3,6 +3,7 @@ package daemon import ( "encoding/json" "errors" + "fmt" "net" "os" "os/signal" @@ -53,6 +54,10 @@ func (d *Daemon) Run(ctx *cli.Context) { } d.bootstrapNode = ctx.Bool("bootstrap") + if err := os.Mkdir("/var/run/netns", 0777); err != nil { + fmt.Println("mkdir /var/run/netns failed", err) + } + go ServeAPI(d) go func() { var bindInterface string diff --git a/scripts/socketplane.sh b/scripts/socketplane.sh index a31a019..2063022 100755 --- a/scripts/socketplane.sh +++ b/scripts/socketplane.sh @@ -21,7 +21,7 @@ COMMANDS: help Help and usage - install [unattended] + install [unattended] [nopowerstrip] Install SocketPlane (installs docker and openvswitch) uninstall @@ -87,28 +87,6 @@ EOF basedir=$(dirname $(readlink -m $0)) . $basedir/functions.sh -# Utility function to attach a port to a network namespace -attach() # OVS Port ID - # IP Address - # Subnet - # MAC Address - # Gateway IP - # Namespace PID -{ - # see: https://docs.docker.com/articles/networking/ - - [ ! -d /var/run/netns ] && mkdir -p /var/run/netns - [ -f /var/run/netns/$6 ] && rm -f /var/run/netns/$6 - ln -s /proc/$6/ns/net /var/run/netns/$6 - - ip link set dev $1 netns $6 - ip netns exec $6 ip link set dev $1 address $4 - ip netns exec $6 ip link set dev $1 up - ip netns exec $6 ip addr add $2$3 dev $1 - ip netns exec $6 ip route add default via $5 - -} - get_status() { OS="NOT_LINUX" RELEASE="NOT_LINUX" @@ -277,7 +255,30 @@ start_socketplane() { flags="--iface=auto" - if [ "$1" = "unattended" ]; then + bstrap=manual + ps=yes + + while true; do + args=$@ + if [ "$args" = "" ]; then + break + fi + + case "$1" in + unattended ) + bstrap=auto + ;; + nopowerstrip ) + ps=no + ;; + *) + log_fatal "Unknown option $1" + ;; + esac + shift + done + + if [ "$bstrap" = "auto" ]; then [ -z $BOOTSTRAP ] && log_fatal "BOOTSTRAP not set" && exit 1 if [ "$BOOTSTRAP" = "true" ] ; then @@ -305,7 +306,10 @@ start_socketplane() { flags="$flags --debug=true" fi - cid=$(docker run -itd --privileged=true --net=host socketplane/socketplane socketplane $flags) + cid=$(docker run --name socketplane -itd --privileged=true \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /usr/bin/docker:/usr/bin/docker -v /proc:/hostproc -e PROCFS=/hostproc \ + --net=host socketplane/socketplane socketplane $flags) if [ -n "$cid" ]; then log_info "A SocketPlane container was started" | indent @@ -316,6 +320,12 @@ start_socketplane() { mkdir -p /var/run/socketplane echo $cid > /var/run/socketplane/cid + + if [ "$ps" = "yes" ]; then + pscid=$(docker run -d --name powerstrip -v /var/run/docker.sock:/var/run/docker.sock \ + -v /etc/socketplane/adapters.yml:/etc/powerstrip/adapters.yml --net=host \ + clusterhq/powerstrip:v0.0.1) + fi } start_socketplane_image() { @@ -368,6 +378,15 @@ stop_socketplane() { log_info "Removing socketplane container: $IMAGE_ID" | indent docker rm $IMAGE_ID > /dev/null done + + for IMAGE_ID in $(docker ps -a | grep clusterhq | awk '{ print $1; }'); do + log_info "Stopping powerstrip container: $IMAGE_ID" | indent + docker stop $IMAGE_ID > /dev/null + sleep 1 + log_info "Removing powerstrip container: $IMAGE_ID" | indent + docker rm $IMAGE_ID > /dev/null + done + log_info "SocketPlane container deleted" | indent } @@ -429,8 +448,6 @@ container_run() { json=$(curl -s -X POST http://localhost:6675/v0.1/connections -d "{ \"container_id\": \"$cid\", \"container_name\": \"$cName\", \"container_pid\": \"$cPid\", \"network\": \"$network\" }") result=$(echo $json | sed 's/[,{}]/\n/g' | sed 's/^".*":"\(.*\)"/\1/g' | awk -v RS="" '{ print $7, $8, $9, $10, $11 }') - attach $result $cPid - if [ "$attach" = "false" ]; then echo $cid else From 6562b6b98c3e844d4c9bc8e81f9281cdeed35c35 Mon Sep 17 00:00:00 2001 From: Jana Radhakrishnan Date: Sat, 7 Feb 2015 01:28:54 +0000 Subject: [PATCH 2/5] Fixed a minor compiler error Signed-off-by: Jana Radhakrishnan --- daemon/bridge.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/bridge.go b/daemon/bridge.go index 49bf9eb..a3d3dd0 100644 --- a/daemon/bridge.go +++ b/daemon/bridge.go @@ -271,7 +271,7 @@ func AddConnection(nspid int, networkName string) (ovsConnection OvsConnection, return } - if err := netns.Set(targetns); err != nil { + if err = netns.Set(targetns); err != nil { return } defer netns.Set(origns) From 0f1bea06de9221d1fc64e0e4b16dc7aef3a864dd Mon Sep 17 00:00:00 2001 From: Jana Radhakrishnan Date: Sat, 7 Feb 2015 02:19:32 +0000 Subject: [PATCH 3/5] Moved powerstrip code to a new file Signed-off-by: Jana Radhakrishnan --- Godeps/Godeps.json | 6 +- daemon/api.go | 129 ---------------------------------------- daemon/powerstrip.go | 137 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 134 deletions(-) create mode 100644 daemon/powerstrip.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 64790be..271bdc7 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "github.com/socketplane/socketplane", - "GoVersion": "go1.3.2", + "GoVersion": "go1.4.1", "Packages": [ "./..." ], @@ -194,10 +194,6 @@ "ImportPath": "github.com/vishvananda/netns", "Rev": "e14a2d4e90ae568a5a78b248da5579f289e25925" }, - { - "ImportPath": "github.com/vishvananda/netns", - "Rev": "e14a2d4e90ae568a5a78b248da5579f289e25925" - }, { "ImportPath": "golang.org/x/net/internal/iana", "Rev": "9dd48c277bcb2bb2cc3eb6a6368a486a567d3562" diff --git a/daemon/api.go b/daemon/api.go index 059bf3a..c8d5644 100644 --- a/daemon/api.go +++ b/daemon/api.go @@ -6,13 +6,9 @@ import ( "net" "net/http" "net/url" - "regexp" - "strconv" - "strings" log "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/Sirupsen/logrus" "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/gorilla/mux" - "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/samalba/dockerclient" ) const API_VERSION string = "/v0.1" @@ -38,39 +34,6 @@ type Connection struct { ConnectionDetails OvsConnection `json:"connection_details"` } -type adapterRequest struct { - PowerstripProtocolVersion int - Type string - ClientRequest struct { - Method string - Request string - Body string - } - ServerResponse struct { - Body string - Code int - ContentType string - } -} - -type adapterPreResponse struct { - PowerstripProtocolVersion int - ModifiedClientRequest struct { - Method string - Request string - Body string - } -} - -type adapterPostResponse struct { - PowerstripProtocolVersion int - ModifiedServerResponse struct { - Body string - Code int - ContentType string - } -} - type apiError struct { Code int Message string @@ -137,98 +100,6 @@ func createRouter(d *Daemon) *mux.Router { return r } -func psAdapterPreHook(d *Daemon, reqParams adapterRequest) *adapterPreResponse { - if reqParams.ClientRequest.Body != "" { - jsonBody := &dockerclient.ContainerConfig{} - err := json.Unmarshal([]byte(reqParams.ClientRequest.Body), &jsonBody) - if err != nil { - fmt.Println("Body JSON unmarsall failed", err) - } - - jsonBody.HostConfig.NetworkMode = "none" - - preResp := &adapterPreResponse{} - preResp.PowerstripProtocolVersion = reqParams.PowerstripProtocolVersion - preResp.ModifiedClientRequest.Method = reqParams.ClientRequest.Method - preResp.ModifiedClientRequest.Request = reqParams.ClientRequest.Request - - body, _ := json.Marshal(jsonBody) - preResp.ModifiedClientRequest.Body = string(body) - - return preResp - } - return nil -} - -func psAdapterPostHook(d *Daemon, reqParams adapterRequest) *adapterPostResponse { - if reqParams.ClientRequest.Request != "" { - // start api looks like this //containers//start - s := regexp.MustCompile("/").Split(reqParams.ClientRequest.Request, 5) - cid := s[3] - - docker, _ := dockerclient.NewDockerClient( - "unix:///var/run/docker.sock", nil) - info, err := docker.InspectContainer(cid) - if err != nil { - fmt.Println("InspectContainer failed", err) - } - - cfg := &Connection{} - - cfg.ContainerID = string(cid) - cfg.ContainerName = info.Name - cfg.ContainerPID = strconv.Itoa(info.State.Pid) - cfg.Network = DefaultNetworkName - for _, env := range info.Config.Env { - val := regexp.MustCompile("=").Split(env, 3) - if val[0] == "SP_NETWORK" { - cfg.Network = strings.Trim(val[1], " ") - } - } - - context := &ConnectionContext{ - ConnectionAdd, - cfg, - make(chan *Connection), - } - d.cC <- context - - <-context.Result - - postResp := &adapterPostResponse{} - postResp.PowerstripProtocolVersion = reqParams.PowerstripProtocolVersion - postResp.ModifiedServerResponse.ContentType = "application/json" - postResp.ModifiedServerResponse.Body = reqParams.ServerResponse.Body - postResp.ModifiedServerResponse.Code = reqParams.ServerResponse.Code - - return postResp - } - - return nil -} - -func psAdapter(d *Daemon, w http.ResponseWriter, r *http.Request) *apiError { - var reqParams adapterRequest - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&reqParams) - if err != nil { - fmt.Println("Error decodeing JSON", err) - //return &apiError{http.StatusInternalServerError, err.Error()} - } - - var data []byte - switch reqParams.Type { - case "pre-hook": - data, _ = json.Marshal(psAdapterPreHook(d, reqParams)) - case "post-hook": - data, _ = json.Marshal(psAdapterPostHook(d, reqParams)) - } - - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.Write(data) - return nil -} - func getConfiguration(d *Daemon, w http.ResponseWriter, r *http.Request) *apiError { data, _ := json.Marshal(d.Configuration) w.Header().Set("Content-Type", "application/json; charset=utf-8") diff --git a/daemon/powerstrip.go b/daemon/powerstrip.go new file mode 100644 index 0000000..0f4d140 --- /dev/null +++ b/daemon/powerstrip.go @@ -0,0 +1,137 @@ +package daemon + +import ( + "encoding/json" + "fmt" + "net/http" + "regexp" + "strconv" + "strings" + + "github.com/socketplane/socketplane/Godeps/_workspace/src/github.com/samalba/dockerclient" +) + +type adapterRequest struct { + PowerstripProtocolVersion int + Type string + ClientRequest struct { + Method string + Request string + Body string + } + ServerResponse struct { + Body string + Code int + ContentType string + } +} + +type adapterPreResponse struct { + PowerstripProtocolVersion int + ModifiedClientRequest struct { + Method string + Request string + Body string + } +} + +type adapterPostResponse struct { + PowerstripProtocolVersion int + ModifiedServerResponse struct { + Body string + Code int + ContentType string + } +} + +func psAdapterPreHook(d *Daemon, reqParams adapterRequest) *adapterPreResponse { + if reqParams.ClientRequest.Body != "" { + jsonBody := &dockerclient.ContainerConfig{} + err := json.Unmarshal([]byte(reqParams.ClientRequest.Body), &jsonBody) + if err != nil { + fmt.Println("Body JSON unmarsall failed", err) + } + + jsonBody.HostConfig.NetworkMode = "none" + + preResp := &adapterPreResponse{} + preResp.PowerstripProtocolVersion = reqParams.PowerstripProtocolVersion + preResp.ModifiedClientRequest.Method = reqParams.ClientRequest.Method + preResp.ModifiedClientRequest.Request = reqParams.ClientRequest.Request + + body, _ := json.Marshal(jsonBody) + preResp.ModifiedClientRequest.Body = string(body) + + return preResp + } + return nil +} + +func psAdapterPostHook(d *Daemon, reqParams adapterRequest) *adapterPostResponse { + if reqParams.ClientRequest.Request != "" { + // start api looks like this //containers//start + s := regexp.MustCompile("/").Split(reqParams.ClientRequest.Request, 5) + cid := s[3] + + docker, _ := dockerclient.NewDockerClient( + "unix:///var/run/docker.sock", nil) + info, err := docker.InspectContainer(cid) + if err != nil { + fmt.Println("InspectContainer failed", err) + } + + cfg := &Connection{} + + cfg.ContainerID = string(cid) + cfg.ContainerName = info.Name + cfg.ContainerPID = strconv.Itoa(info.State.Pid) + cfg.Network = DefaultNetworkName + for _, env := range info.Config.Env { + val := regexp.MustCompile("=").Split(env, 3) + if val[0] == "SP_NETWORK" { + cfg.Network = strings.Trim(val[1], " ") + } + } + + context := &ConnectionContext{ + ConnectionAdd, + cfg, + make(chan *Connection), + } + d.cC <- context + + <-context.Result + + postResp := &adapterPostResponse{} + postResp.PowerstripProtocolVersion = reqParams.PowerstripProtocolVersion + postResp.ModifiedServerResponse.ContentType = "application/json" + postResp.ModifiedServerResponse.Body = reqParams.ServerResponse.Body + postResp.ModifiedServerResponse.Code = reqParams.ServerResponse.Code + + return postResp + } + + return nil +} + +func psAdapter(d *Daemon, w http.ResponseWriter, r *http.Request) *apiError { + var reqParams adapterRequest + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&reqParams) + if err != nil { + fmt.Println("Error decodeing JSON", err) + //return &apiError{http.StatusInternalServerError, err.Error()} + } + + var data []byte + switch reqParams.Type { + case "pre-hook": + data, _ = json.Marshal(psAdapterPreHook(d, reqParams)) + case "post-hook": + data, _ = json.Marshal(psAdapterPostHook(d, reqParams)) + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Write(data) + return nil +} From 6b136b94a3ac712a80e2789a71f81f9a5737fe33 Mon Sep 17 00:00:00 2001 From: Jana Radhakrishnan Date: Sat, 7 Feb 2015 04:46:50 +0000 Subject: [PATCH 4/5] Added support for non-vagrant deployment scenarios Signed-off-by: Jana Radhakrishnan --- scripts/install.sh | 4 ++++ scripts/socketplane.sh | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/scripts/install.sh b/scripts/install.sh index 325753b..8a2ccb8 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -46,6 +46,10 @@ if [ ! -f /opt/socketplane/functions.sh ]; then $curl /opt/socketplane/functions.sh https://raw.githubusercontent.com/socketplane/socketplane/master/scripts/functions.sh fi +if [ ! -f /etc/socketplane/adapters.yml ]; then + $curl /etc/socketplane/adapters.yml https://raw.githubusercontent.com/socketplane/socketplane/master/adapters.yml +fi + chmod +x /opt/socketplane/socketplane if [ ! -f /usr/bin/socketplane ]; then diff --git a/scripts/socketplane.sh b/scripts/socketplane.sh index 2063022..8f6b3b4 100755 --- a/scripts/socketplane.sh +++ b/scripts/socketplane.sh @@ -322,6 +322,11 @@ start_socketplane() { echo $cid > /var/run/socketplane/cid if [ "$ps" = "yes" ]; then + if [ ! -f /etc/socketplane/adapters.yml ]; then + mkdir -p /etc/socketplane + cp $PWD/adapters.yml /etc/socketplane + fi + pscid=$(docker run -d --name powerstrip -v /var/run/docker.sock:/var/run/docker.sock \ -v /etc/socketplane/adapters.yml:/etc/powerstrip/adapters.yml --net=host \ clusterhq/powerstrip:v0.0.1) From 78c1703b6be51b57823e7dafa8bdddcd0b9c5801 Mon Sep 17 00:00:00 2001 From: Jana Radhakrishnan Date: Sat, 7 Feb 2015 05:02:30 +0000 Subject: [PATCH 5/5] Added powerstrip.md Signed-off-by: Jana Radhakrishnan --- powerstrip.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 powerstrip.md diff --git a/powerstrip.md b/powerstrip.md new file mode 100644 index 0000000..ab6e007 --- /dev/null +++ b/powerstrip.md @@ -0,0 +1,13 @@ +# Powerstrip mode + +Using powerstrip mode of socketplane is simple and easy. If you are not using vagrant run "socketplane install" from the base directory of the socketplane workspace or just run scripts/install.sh to setup everything. If you are using vagrant no other extra steps are required. + +Once socketplane is installed run new containers using the well known docker commands as below example shows: + + sudo DOCKER_HOST=localhost:2375 docker run -itd ubuntu + +If you want the containers to connect to different network just add a special environment variable as follows: + + sudo DOCKER_HOST=localhost:2375 docker run -e SP_NETWORK=test -itd ubuntu + +The above commands assumes that you have already created a network named "test" using the already existing socketplane commands. \ No newline at end of file