<commit_before>// Copyright © 2014-2015, Christian Vozar
// Licensed under the MIT License.
// http://opensource.org/licenses/MIT

package hipchat

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/mozilla-services/heka/message"
	. "github.com/mozilla-services/heka/pipeline"
	"io/ioutil"
	"net/http"
	"net/url"
)

const (
	// Atlassian HipChat API Version
	HipChatAPIVersion = "v1"
)

// HipchatOutput maintains high-level configuration options for the plugin.
type HipchatOutput struct {
	conf   *HipchatOutputConfig
	url    string
	format string
}

// Hipchat Output Configuration via Heka TOML
type HipchatOutputConfig struct {
	// Outputs the payload attribute in the HipChat message vs a full JSON message dump
	PayloadOnly bool `toml:"payload_only"`
	// HipChat Authorization token. Notification token is appropriate.
	AuthToken string `toml:"auth_token"`
	// Required. ID or name of the room.
	RoomID string `toml:"room_id"`
	// Required. Name the message will appear be sent. Must be less than 15
	// characters long. May contain letters, numbers, -, _, and spaces.
	From string
	// Whether or not this message should trigger a notification for people
	// in the room (change the tab color, play a sound, etc).
	// Each recipient's notification preferences are taken into account.
	// Default is false
	Notify bool
}

func (ho *HipchatOutput) ConfigStruct() interface{} {
	return &HipchatOutputConfig{
		PayloadOnly: true,
		From:        "Heka",
		Notify:      false,
	}
}

func (ho *HipchatOutput) sendMessage(mc string, s int32) error {
	messageURI := fmt.Sprintf("%s/rooms/message?auth_token=%s", ho.url, url.QueryEscape(ho.conf.AuthToken))

	messagePayload := url.Values{
		"room_id":        {ho.conf.RoomID},
		"from":           {ho.conf.From},
		"message":        {mc},
		"message_format": {ho.format},
	}

	if ho.conf.Notify == true {
		messagePayload.Add("notify", "1")
	}

	switch s {
	case 0, 1, 2, 3:
		messagePayload.Add("color", "red")
	case 4:
		messagePayload.Add("color", "yellow")
	case 5, 6:
		messagePayload.Add("color", "green")
	default:
		messagePayload.Add("color", "gray")
	}

	resp, err := http.PostForm(messageURI, messagePayload)
	if err != nil {
		return err
	}

	switch resp.StatusCode {
	case 400:
		return errors.New("Bad request.")
	case 401:
		return errors.New("Provided authentication rejected.")
	case 403:
		return errors.New("Rate limit exceeded.")
	case 406:
		return errors.New("Message contains invalid content type.")
	case 500:
		return errors.New("Internal server error.")
	case 503:
		return errors.New("Service unavailable.")
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	messageResponse := &struct{ Status string }{}
	if err := json.Unmarshal(body, messageResponse); err != nil {
		return err
	}
	if messageResponse.Status != "sent" {
		return errors.New("Status response was not sent.")
	}

	return nil
}

func (ho *HipchatOutput) Init(config interface{}) (err error) {
	ho.conf = config.(*HipchatOutputConfig)

	if ho.conf.RoomID == "" {
		return fmt.Errorf("room_id must contain a HipChat room ID or name")
	}

	if len(ho.conf.From) > 15 {
		return fmt.Errorf("from must be less than 15 characters")
	}

	ho.url = fmt.Sprintf("https://api.hipchat.com/%s", HipChatAPIVersion)
	ho.format = "text"
	return
}

func (ho *HipchatOutput) Run(or OutputRunner, h PluginHelper) (err error) {
	inChan := or.InChan()

	var (
		pack     *PipelinePack
		msg      *message.Message
		contents []byte
	)

	for pack = range inChan {
		msg = pack.Message
		if ho.conf.PayloadOnly {
			err = ho.sendMessage(msg.GetPayload(), msg.GetSeverity())
		} else {
			if contents, err = json.Marshal(msg); err == nil {
				err = ho.sendMessage(string(contents), msg.GetSeverity())
			} else {
				or.LogError(err)
			}
		}
		if err != nil {
			or.LogError(err)
		}
		pack.Recycle()
	}
	return
}

func init() {
	RegisterPlugin("HipchatOutput", func() interface{} {
		return new(HipchatOutput)
	})
}
<commit_msg>comment on exported const HipChatAPIVersion should be of the form "HipChatAPIVersion ..."<commit_after>// Copyright © 2014-2015, Christian Vozar
// Licensed under the MIT License.
// http://opensource.org/licenses/MIT

package hipchat

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/mozilla-services/heka/message"
	. "github.com/mozilla-services/heka/pipeline"
	"io/ioutil"
	"net/http"
	"net/url"
)

const (
	// HipChatAPIVersion is the Atlassian HipChat API Version to utilize.
	HipChatAPIVersion = "v1"
)

// HipchatOutput maintains high-level configuration options for the plugin.
type HipchatOutput struct {
	conf   *HipchatOutputConfig
	url    string
	format string
}

// Hipchat Output Configuration via Heka TOML
type HipchatOutputConfig struct {
	// Outputs the payload attribute in the HipChat message vs a full JSON message dump
	PayloadOnly bool `toml:"payload_only"`
	// HipChat Authorization token. Notification token is appropriate.
	AuthToken string `toml:"auth_token"`
	// Required. ID or name of the room.
	RoomID string `toml:"room_id"`
	// Required. Name the message will appear be sent. Must be less than 15
	// characters long. May contain letters, numbers, -, _, and spaces.
	From string
	// Whether or not this message should trigger a notification for people
	// in the room (change the tab color, play a sound, etc).
	// Each recipient's notification preferences are taken into account.
	// Default is false
	Notify bool
}

func (ho *HipchatOutput) ConfigStruct() interface{} {
	return &HipchatOutputConfig{
		PayloadOnly: true,
		From:        "Heka",
		Notify:      false,
	}
}

func (ho *HipchatOutput) sendMessage(mc string, s int32) error {
	messageURI := fmt.Sprintf("%s/rooms/message?auth_token=%s", ho.url, url.QueryEscape(ho.conf.AuthToken))

	messagePayload := url.Values{
		"room_id":        {ho.conf.RoomID},
		"from":           {ho.conf.From},
		"message":        {mc},
		"message_format": {ho.format},
	}

	if ho.conf.Notify == true {
		messagePayload.Add("notify", "1")
	}

	switch s {
	case 0, 1, 2, 3:
		messagePayload.Add("color", "red")
	case 4:
		messagePayload.Add("color", "yellow")
	case 5, 6:
		messagePayload.Add("color", "green")
	default:
		messagePayload.Add("color", "gray")
	}

	resp, err := http.PostForm(messageURI, messagePayload)
	if err != nil {
		return err
	}

	switch resp.StatusCode {
	case 400:
		return errors.New("Bad request.")
	case 401:
		return errors.New("Provided authentication rejected.")
	case 403:
		return errors.New("Rate limit exceeded.")
	case 406:
		return errors.New("Message contains invalid content type.")
	case 500:
		return errors.New("Internal server error.")
	case 503:
		return errors.New("Service unavailable.")
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	messageResponse := &struct{ Status string }{}
	if err := json.Unmarshal(body, messageResponse); err != nil {
		return err
	}
	if messageResponse.Status != "sent" {
		return errors.New("Status response was not sent.")
	}

	return nil
}

func (ho *HipchatOutput) Init(config interface{}) (err error) {
	ho.conf = config.(*HipchatOutputConfig)

	if ho.conf.RoomID == "" {
		return fmt.Errorf("room_id must contain a HipChat room ID or name")
	}

	if len(ho.conf.From) > 15 {
		return fmt.Errorf("from must be less than 15 characters")
	}

	ho.url = fmt.Sprintf("https://api.hipchat.com/%s", HipChatAPIVersion)
	ho.format = "text"
	return
}

func (ho *HipchatOutput) Run(or OutputRunner, h PluginHelper) (err error) {
	inChan := or.InChan()

	var (
		pack     *PipelinePack
		msg      *message.Message
		contents []byte
	)

	for pack = range inChan {
		msg = pack.Message
		if ho.conf.PayloadOnly {
			err = ho.sendMessage(msg.GetPayload(), msg.GetSeverity())
		} else {
			if contents, err = json.Marshal(msg); err == nil {
				err = ho.sendMessage(string(contents), msg.GetSeverity())
			} else {
				or.LogError(err)
			}
		}
		if err != nil {
			or.LogError(err)
		}
		pack.Recycle()
	}
	return
}

func init() {
	RegisterPlugin("HipchatOutput", func() interface{} {
		return new(HipchatOutput)
	})
}
<|endoftext|>


<commit_before>package ccv3_test

import (
	"fmt"
	"net/http"

	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
	"code.cloudfoundry.org/cli/types"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/ghttp"
)

var _ = Describe("Space Quotas", func() {
	var client *Client
	var executeErr error
	var warnings Warnings
	var inputSpaceQuota SpaceQuota
	var createdSpaceQuota SpaceQuota

	BeforeEach(func() {
		client, _ = NewTestClient()
	})

	Describe("CreateSpaceQuotas", func() {
		JustBeforeEach(func() {
			createdSpaceQuota, warnings, executeErr = client.CreateSpaceQuota(inputSpaceQuota)
		})

		When("successfully creating a space quota without spaces", func() {
			BeforeEach(func() {
				inputSpaceQuota = SpaceQuota{
					Name: "my-space-quota",
					Apps: AppLimit{
						TotalMemory:       types.NullInt{IsSet: true, Value: 2},
						InstanceMemory:    types.NullInt{IsSet: true, Value: 3},
						TotalAppInstances: types.NullInt{IsSet: true, Value: 4},
					},
					Services: ServiceLimit{
						PaidServicePlans:      true,
						TotalServiceInstances: types.NullInt{IsSet: true, Value: 5},
					},
					Routes: RouteLimit{
						TotalRoutes:        types.NullInt{IsSet: true, Value: 6},
						TotalReservedPorts: types.NullInt{IsSet: true, Value: 7},
					},
					OrgGUID: "some-org-guid",
				}

				response := fmt.Sprintf(`{
  "guid": "space-quota-guid",
  "created_at": "2016-05-04T17:00:41Z",
  "updated_at": "2016-05-04T17:00:41Z",
  "name": "my-space-quota",
  "apps": {
    "total_memory_in_mb": 2,
    "per_process_memory_in_mb": 3,
    "total_instances": 4,
    "per_app_tasks": 5
  },
  "services": {
    "paid_services_allowed": true,
    "total_service_instances": 6,
    "total_service_keys": 7
  },
  "routes": {
    "total_routes": 8,
    "total_reserved_ports": 9
  },
  "relationships": {
    "organization": {
      "data": {
        "guid": "some-org-guid"
      }
    },
    "spaces": {
      "data": []
    }
  },
  "links": {
    "self": {
      "href": "https://api.example.org/v3/organization_quotas/9b370018-c38e-44c9-86d6-155c76801104"
    },
    "organization": {
      "href": "https://api.example.org/v3/organizations/9b370018-c38e-44c9-86d6-155c76801104"
    }
  }
}
`)
				expectedBody := map[string]interface{}{
					"name": "my-space-quota",
					"relationships": map[string]interface{}{
						"organization": map[string]interface{}{
							"data": map[string]interface{}{
								"guid": "some-org-guid",
							},
						},
					},
				}

				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPost, "/v3/space_quotas"),
						VerifyJSONRepresenting(expectedBody),
						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"space-quota-warning"}}),
					),
				)
			})

			It("returns space quota and warnings", func() {
				Expect(executeErr).NotTo(HaveOccurred())
				Expect(warnings).To(ConsistOf("space-quota-warning"))
				Expect(createdSpaceQuota).To(Equal(
					SpaceQuota{
						GUID: "space-quota-guid",
						Name: "my-space-quota",
						Apps: AppLimit{
							TotalMemory:       types.NullInt{IsSet: true, Value: 2},
							InstanceMemory:    types.NullInt{IsSet: true, Value: 3},
							TotalAppInstances: types.NullInt{IsSet: true, Value: 4},
						},
						Services: ServiceLimit{
							PaidServicePlans:      true,
							TotalServiceInstances: types.NullInt{IsSet: true, Value: 5},
						},
						Routes: RouteLimit{
							TotalRoutes:        types.NullInt{IsSet: true, Value: 6},
							TotalReservedPorts: types.NullInt{IsSet: true, Value: 7},
						},
						OrgGUID: "some-org-guid",
					}))
			})
		})

		When("successfully creating a space quota with spaces", func() {
			BeforeEach(func() {
				inputSpaceQuota = SpaceQuota{
					Name: "my-space-quota",
					Apps: AppLimit{
						TotalMemory:       types.NullInt{IsSet: true, Value: 2},
						InstanceMemory:    types.NullInt{IsSet: true, Value: 3},
						TotalAppInstances: types.NullInt{IsSet: true, Value: 4},
					},
					Services: ServiceLimit{
						PaidServicePlans:      true,
						TotalServiceInstances: types.NullInt{IsSet: true, Value: 6},
					},
					Routes: RouteLimit{
						TotalRoutes:        types.NullInt{IsSet: true, Value: 8},
						TotalReservedPorts: types.NullInt{IsSet: true, Value: 9},
					},
					OrgGUID:    "some-org-guid",
					SpaceGUIDs: []string{"space-guid-1", "space-guid-2"},
				}

				response := fmt.Sprintf(`{
  "guid": "space-quota-guid",
  "created_at": "2016-05-04T17:00:41Z",
  "updated_at": "2016-05-04T17:00:41Z",
  "name": "my-space-quota",
  "apps": {
    "total_memory_in_mb": 2,
    "per_process_memory_in_mb": 3,
    "total_instances": 4,
    "per_app_tasks": 5
  },
  "services": {
    "paid_services_allowed": true,
    "total_service_instances": 6,
    "total_service_keys": 7
  },
  "routes": {
    "total_routes": 8,
    "total_reserved_ports": 9
  },
  "relationships": {
    "organization": {
      "data": {
        "guid": "some-org-guid"
      }
    },
    "spaces": {
      "data": [{"guid": "space-guid-1"}, {"guid": "space-guid-2"}]
    }
  },
  "links": {
    "self": {
      "href": "https://api.example.org/v3/organization_quotas/9b370018-c38e-44c9-86d6-155c76801104"
    },
    "organization": {
      "href": "https://api.example.org/v3/organizations/9b370018-c38e-44c9-86d6-155c76801104"
    }
  }
}
`)

				expectedBody := map[string]interface{}{
					"name": "my-space-quota",
					"apps": map[string]interface{}{
						"total_memory_in_mb":       2,
						"per_process_memory_in_mb": 3,
						"total_instances":          4,
					},
					"services": map[string]interface{}{
						"paid_services_allowed":   true,
						"total_service_instances": 6,
					},
					"routes": map[string]interface{}{
						"total_routes":         8,
						"total_reserved_ports": 9,
					},
					"relationships": map[string]interface{}{
						"organization": map[string]interface{}{
							"data": map[string]interface{}{
								"guid": "some-org-guid",
							},
						},
						"spaces": map[string]interface{}{
							"data": []map[string]interface{}{
								{"guid": "space-guid-1"},
								{"guid": "space-guid-2"},
							},
						},
					},
				}

				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPost, "/v3/space_quotas"),
						VerifyJSONRepresenting(expectedBody),
						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"space-quota-warning"}}),
					),
				)
			})

			It("returns space quota and warnings", func() {
				Expect(executeErr).NotTo(HaveOccurred())
				Expect(warnings).To(ConsistOf("space-quota-warning"))
				Expect(createdSpaceQuota).To(Equal(
					SpaceQuota{
						GUID: "space-quota-guid",
						Name: "my-space-quota",
						Apps: AppLimit{
							TotalMemory:       types.NullInt{IsSet: true, Value: 2},
							InstanceMemory:    types.NullInt{IsSet: true, Value: 3},
							TotalAppInstances: types.NullInt{IsSet: true, Value: 4},
						},
						Services: ServiceLimit{
							PaidServicePlans:      true,
							TotalServiceInstances: types.NullInt{IsSet: true, Value: 6},
						},
						Routes: RouteLimit{
							TotalRoutes:        types.NullInt{IsSet: true, Value: 8},
							TotalReservedPorts: types.NullInt{IsSet: true, Value: 9},
						},
						OrgGUID:    "some-org-guid",
						SpaceGUIDs: []string{"space-guid-1", "space-guid-2"},
					}))
			})
		})

		When("the cloud controller returns errors and warnings", func() {
			BeforeEach(func() {
				response := `{
					"errors": [
						{
							"code": 10008,
							"detail": "The request is semantically invalid: command presence",
							"title": "CF-UnprocessableEntity"
						},
						{
							"code": 10010,
							"detail": "App not found",
							"title": "CF-ResourceNotFound"
						}
					]
				}`
				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPost, "/v3/space_quotas"),
						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("returns the error and all warnings", func() {
				Expect(executeErr).To(MatchError(ccerror.MultiError{
					ResponseCode: http.StatusTeapot,
					Errors: []ccerror.V3Error{
						{
							Code:   10008,
							Detail: "The request is semantically invalid: command presence",
							Title:  "CF-UnprocessableEntity",
						},
						{
							Code:   10010,
							Detail: "App not found",
							Title:  "CF-ResourceNotFound",
						},
					},
				}))
				Expect(warnings).To(ConsistOf("this is a warning"))
			})
		})
	})
})
<commit_msg>Fix space quota test expectation<commit_after>package ccv3_test

import (
	"fmt"
	"net/http"

	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
	"code.cloudfoundry.org/cli/types"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/ghttp"
)

var _ = Describe("Space Quotas", func() {
	var client *Client
	var executeErr error
	var warnings Warnings
	var inputSpaceQuota SpaceQuota
	var createdSpaceQuota SpaceQuota

	BeforeEach(func() {
		client, _ = NewTestClient()
	})

	Describe("CreateSpaceQuotas", func() {
		JustBeforeEach(func() {
			createdSpaceQuota, warnings, executeErr = client.CreateSpaceQuota(inputSpaceQuota)
		})

		When("successfully creating a space quota without spaces", func() {
			BeforeEach(func() {
				inputSpaceQuota = SpaceQuota{
					Name: "my-space-quota",
					Apps: AppLimit{
						TotalMemory:       types.NullInt{IsSet: true, Value: 2},
						InstanceMemory:    types.NullInt{IsSet: true, Value: 3},
						TotalAppInstances: types.NullInt{IsSet: true, Value: 4},
					},
					Services: ServiceLimit{
						PaidServicePlans:      true,
						TotalServiceInstances: types.NullInt{IsSet: true, Value: 5},
					},
					Routes: RouteLimit{
						TotalRoutes:        types.NullInt{IsSet: true, Value: 6},
						TotalReservedPorts: types.NullInt{IsSet: true, Value: 7},
					},
					OrgGUID: "some-org-guid",
				}

				response := fmt.Sprintf(`{
  "guid": "space-quota-guid",
  "created_at": "2016-05-04T17:00:41Z",
  "updated_at": "2016-05-04T17:00:41Z",
  "name": "my-space-quota",
  "apps": {
    "total_memory_in_mb": 2,
    "per_process_memory_in_mb": 3,
    "total_instances": 4,
    "per_app_tasks": 900
  },
  "services": {
    "paid_services_allowed": true,
    "total_service_instances": 5,
    "total_service_keys": 700
  },
  "routes": {
    "total_routes": 6,
    "total_reserved_ports": 7
  },
  "relationships": {
    "organization": {
      "data": {
        "guid": "some-org-guid"
      }
    },
    "spaces": {
      "data": []
    }
  },
  "links": {
    "self": {
      "href": "https://api.example.org/v3/organization_quotas/9b370018-c38e-44c9-86d6-155c76801104"
    },
    "organization": {
      "href": "https://api.example.org/v3/organizations/9b370018-c38e-44c9-86d6-155c76801104"
    }
  }
}
`)
				expectedBody := map[string]interface{}{
					"name": "my-space-quota",
					"apps": map[string]interface{}{
						"total_memory_in_mb":       2,
						"per_process_memory_in_mb": 3,
						"total_instances":          4,
					},
					"services": map[string]interface{}{
						"paid_services_allowed":   true,
						"total_service_instances": 5,
					},
					"routes": map[string]interface{}{
						"total_routes":         6,
						"total_reserved_ports": 7,
					},
					"relationships": map[string]interface{}{
						"organization": map[string]interface{}{
							"data": map[string]interface{}{
								"guid": "some-org-guid",
							},
						},
					},
				}

				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPost, "/v3/space_quotas"),
						VerifyJSONRepresenting(expectedBody),
						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"space-quota-warning"}}),
					),
				)
			})

			It("returns space quota and warnings", func() {
				Expect(executeErr).NotTo(HaveOccurred())
				Expect(warnings).To(ConsistOf("space-quota-warning"))
				Expect(createdSpaceQuota).To(Equal(
					SpaceQuota{
						GUID: "space-quota-guid",
						Name: "my-space-quota",
						Apps: AppLimit{
							TotalMemory:       types.NullInt{IsSet: true, Value: 2},
							InstanceMemory:    types.NullInt{IsSet: true, Value: 3},
							TotalAppInstances: types.NullInt{IsSet: true, Value: 4},
						},
						Services: ServiceLimit{
							PaidServicePlans:      true,
							TotalServiceInstances: types.NullInt{IsSet: true, Value: 5},
						},
						Routes: RouteLimit{
							TotalRoutes:        types.NullInt{IsSet: true, Value: 6},
							TotalReservedPorts: types.NullInt{IsSet: true, Value: 7},
						},
						OrgGUID: "some-org-guid",
					}))
			})
		})

		When("successfully creating a space quota with spaces", func() {
			BeforeEach(func() {
				inputSpaceQuota = SpaceQuota{
					Name: "my-space-quota",
					Apps: AppLimit{
						TotalMemory:       types.NullInt{IsSet: true, Value: 2},
						InstanceMemory:    types.NullInt{IsSet: true, Value: 3},
						TotalAppInstances: types.NullInt{IsSet: true, Value: 4},
					},
					Services: ServiceLimit{
						PaidServicePlans:      true,
						TotalServiceInstances: types.NullInt{IsSet: true, Value: 6},
					},
					Routes: RouteLimit{
						TotalRoutes:        types.NullInt{IsSet: true, Value: 8},
						TotalReservedPorts: types.NullInt{IsSet: true, Value: 9},
					},
					OrgGUID:    "some-org-guid",
					SpaceGUIDs: []string{"space-guid-1", "space-guid-2"},
				}

				response := fmt.Sprintf(`{
  "guid": "space-quota-guid",
  "created_at": "2016-05-04T17:00:41Z",
  "updated_at": "2016-05-04T17:00:41Z",
  "name": "my-space-quota",
  "apps": {
    "total_memory_in_mb": 2,
    "per_process_memory_in_mb": 3,
    "total_instances": 4,
    "per_app_tasks": 5
  },
  "services": {
    "paid_services_allowed": true,
    "total_service_instances": 6,
    "total_service_keys": 7
  },
  "routes": {
    "total_routes": 8,
    "total_reserved_ports": 9
  },
  "relationships": {
    "organization": {
      "data": {
        "guid": "some-org-guid"
      }
    },
    "spaces": {
      "data": [{"guid": "space-guid-1"}, {"guid": "space-guid-2"}]
    }
  },
  "links": {
    "self": {
      "href": "https://api.example.org/v3/organization_quotas/9b370018-c38e-44c9-86d6-155c76801104"
    },
    "organization": {
      "href": "https://api.example.org/v3/organizations/9b370018-c38e-44c9-86d6-155c76801104"
    }
  }
}
`)

				expectedBody := map[string]interface{}{
					"name": "my-space-quota",
					"apps": map[string]interface{}{
						"total_memory_in_mb":       2,
						"per_process_memory_in_mb": 3,
						"total_instances":          4,
					},
					"services": map[string]interface{}{
						"paid_services_allowed":   true,
						"total_service_instances": 6,
					},
					"routes": map[string]interface{}{
						"total_routes":         8,
						"total_reserved_ports": 9,
					},
					"relationships": map[string]interface{}{
						"organization": map[string]interface{}{
							"data": map[string]interface{}{
								"guid": "some-org-guid",
							},
						},
						"spaces": map[string]interface{}{
							"data": []map[string]interface{}{
								{"guid": "space-guid-1"},
								{"guid": "space-guid-2"},
							},
						},
					},
				}

				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPost, "/v3/space_quotas"),
						VerifyJSONRepresenting(expectedBody),
						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"space-quota-warning"}}),
					),
				)
			})

			It("returns space quota and warnings", func() {
				Expect(executeErr).NotTo(HaveOccurred())
				Expect(warnings).To(ConsistOf("space-quota-warning"))
				Expect(createdSpaceQuota).To(Equal(
					SpaceQuota{
						GUID: "space-quota-guid",
						Name: "my-space-quota",
						Apps: AppLimit{
							TotalMemory:       types.NullInt{IsSet: true, Value: 2},
							InstanceMemory:    types.NullInt{IsSet: true, Value: 3},
							TotalAppInstances: types.NullInt{IsSet: true, Value: 4},
						},
						Services: ServiceLimit{
							PaidServicePlans:      true,
							TotalServiceInstances: types.NullInt{IsSet: true, Value: 6},
						},
						Routes: RouteLimit{
							TotalRoutes:        types.NullInt{IsSet: true, Value: 8},
							TotalReservedPorts: types.NullInt{IsSet: true, Value: 9},
						},
						OrgGUID:    "some-org-guid",
						SpaceGUIDs: []string{"space-guid-1", "space-guid-2"},
					}))
			})
		})

		When("the cloud controller returns errors and warnings", func() {
			BeforeEach(func() {
				response := `{
					"errors": [
						{
							"code": 10008,
							"detail": "The request is semantically invalid: command presence",
							"title": "CF-UnprocessableEntity"
						},
						{
							"code": 10010,
							"detail": "App not found",
							"title": "CF-ResourceNotFound"
						}
					]
				}`
				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPost, "/v3/space_quotas"),
						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("returns the error and all warnings", func() {
				Expect(executeErr).To(MatchError(ccerror.MultiError{
					ResponseCode: http.StatusTeapot,
					Errors: []ccerror.V3Error{
						{
							Code:   10008,
							Detail: "The request is semantically invalid: command presence",
							Title:  "CF-UnprocessableEntity",
						},
						{
							Code:   10010,
							Detail: "App not found",
							Title:  "CF-ResourceNotFound",
						},
					},
				}))
				Expect(warnings).To(ConsistOf("this is a warning"))
			})
		})
	})
})
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"

	"github.com/lxc/lxd"
	"github.com/lxc/lxd/lxc/config"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/gnuflag"
	"github.com/lxc/lxd/shared/i18n"
)

type copyCmd struct {
	profArgs      profileList
	confArgs      configList
	ephem         bool
	containerOnly bool
}

func (c *copyCmd) showByDefault() bool {
	return true
}

func (c *copyCmd) usage() string {
	return i18n.G(
		`Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] [--ephemeral|e] [--profile|-p <profile>...] [--config|-c <key=value>...] [--container-only]

Copy containers within or in between LXD instances.`)
}

func (c *copyCmd) flags() {
	gnuflag.Var(&c.confArgs, "config", i18n.G("Config key/value to apply to the new container"))
	gnuflag.Var(&c.confArgs, "c", i18n.G("Config key/value to apply to the new container"))
	gnuflag.Var(&c.profArgs, "profile", i18n.G("Profile to apply to the new container"))
	gnuflag.Var(&c.profArgs, "p", i18n.G("Profile to apply to the new container"))
	gnuflag.BoolVar(&c.ephem, "ephemeral", false, i18n.G("Ephemeral container"))
	gnuflag.BoolVar(&c.ephem, "e", false, i18n.G("Ephemeral container"))
	gnuflag.BoolVar(&c.containerOnly, "container-only", false, i18n.G("Copy the container without its snapshots"))
}

func (c *copyCmd) copyContainer(conf *config.Config, sourceResource string, destResource string, keepVolatile bool, ephemeral int, stateful bool, containerOnly bool) error {
	sourceRemote, sourceName, err := conf.ParseRemote(sourceResource)
	if err != nil {
		return err
	}

	destRemote, destName, err := conf.ParseRemote(destResource)
	if err != nil {
		return err
	}

	if sourceName == "" {
		return fmt.Errorf(i18n.G("you must specify a source container name"))
	}

	if destName == "" && destResource != "" {
		destName = sourceName
	}

	source, err := lxd.NewClient(conf.Legacy(), sourceRemote)
	if err != nil {
		return err
	}

	var status struct {
		Architecture string
		Devices      map[string]map[string]string
		Config       map[string]string
		Profiles     []string
	}

	// TODO: presumably we want to do this for copying snapshots too? We
	// need to think a bit more about how we track the baseImage in the
	// face of LVM and snapshots in general; this will probably make more
	// sense once that work is done.
	baseImage := ""

	if !shared.IsSnapshot(sourceName) {
		result, err := source.ContainerInfo(sourceName)
		if err != nil {
			return err
		}

		status.Architecture = result.Architecture
		status.Devices = result.Devices
		status.Config = result.Config
		status.Profiles = result.Profiles

	} else {
		result, err := source.SnapshotInfo(sourceName)
		if err != nil {
			return err
		}

		status.Architecture = result.Architecture
		status.Devices = result.Devices
		status.Config = result.Config
		status.Profiles = result.Profiles
	}

	if c.profArgs != nil {
		status.Profiles = append(status.Profiles, c.profArgs...)
	}

	if configMap != nil {
		for key, value := range configMap {
			status.Config[key] = value
		}
	}

	baseImage = status.Config["volatile.base_image"]

	if !keepVolatile {
		for k := range status.Config {
			if strings.HasPrefix(k, "volatile") {
				delete(status.Config, k)
			}
		}
	}

	// Do a local copy if the remotes are the same, otherwise do a migration
	if sourceRemote == destRemote {
		if sourceName == destName {
			return fmt.Errorf(i18n.G("can't copy to the same container name"))
		}

		cp, err := source.LocalCopy(sourceName, destName, status.Config, status.Profiles, ephemeral == 1, containerOnly)
		if err != nil {
			return err
		}

		err = source.WaitForSuccess(cp.Operation)
		if err != nil {
			return err
		}

		if destResource == "" {
			op, err := cp.MetadataAsOperation()
			if err != nil {
				return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
			}

			containers, ok := op.Resources["containers"]
			if !ok || len(containers) == 0 {
				return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
			}

			fields := strings.Split(containers[0], "/")
			fmt.Printf(i18n.G("Container name is: %s")+"\n", fields[len(fields)-1])
		}

		return nil
	}

	dest, err := lxd.NewClient(conf.Legacy(), destRemote)
	if err != nil {
		return err
	}

	sourceProfs := shared.NewStringSet(status.Profiles)
	destProfs := []string{}

	profiles, err := dest.ListProfiles()
	if err != nil {
		return err
	}

	for _, profile := range profiles {
		destProfs = append(destProfs, profile.Name)
	}

	if !sourceProfs.IsSubset(shared.NewStringSet(destProfs)) {
		return fmt.Errorf(i18n.G("not all the profiles from the source exist on the target"))
	}

	if ephemeral == -1 {
		ct, err := source.ContainerInfo(sourceName)
		if err != nil {
			return err
		}

		if ct.Ephemeral {
			ephemeral = 1
		} else {
			ephemeral = 0
		}
	}

	sourceWSResponse, err := source.GetMigrationSourceWS(sourceName, stateful, containerOnly)
	if err != nil {
		return err
	}

	secrets := map[string]string{}

	op, err := sourceWSResponse.MetadataAsOperation()
	if err != nil {
		return err
	}

	for k, v := range op.Metadata {
		secrets[k] = v.(string)
	}

	addresses, err := source.Addresses()
	if err != nil {
		return err
	}

	/* Since we're trying a bunch of different network ports that
	 * may be invalid, we can get "bad handshake" errors when the
	 * websocket code tries to connect. If the first error is a
	 * real error, but the subsequent errors are only network
	 * errors, we should try to report the first real error. Of
	 * course, if all the errors are websocket errors, let's just
	 * report that.
	 */
	waitchan := make(chan map[int]error, 2)
	wait := func(cli *lxd.Client, op string, ch chan map[int]error, senderid int) {
		ch <- map[int]error{senderid: cli.WaitForSuccess(op)}
	}

	var migrationErrFromClient error
	for _, addr := range addresses {
		var migration *api.Response

		sourceWSUrl := "https://" + addr + sourceWSResponse.Operation
		migration, migrationErrFromClient = dest.MigrateFrom(destName, sourceWSUrl, source.Certificate, secrets, status.Architecture, status.Config, status.Devices, status.Profiles, baseImage, ephemeral == 1, false, source, sourceWSResponse.Operation, containerOnly)
		if migrationErrFromClient != nil {
			continue
		}

		// If push mode is implemented then MigrateFrom will return a
		// non-waitable operation. So this needs to be conditionalized
		// on pull mode.
		destOpId := 0
		go wait(dest, migration.Operation, waitchan, destOpId)
		sourceOpId := 1
		go wait(source, sourceWSResponse.Operation, waitchan, sourceOpId)

		var sourceOpErr error
		var destOpErr error
		for i := 0; i < cap(waitchan); i++ {
			tmp := <-waitchan
			err, ok := tmp[sourceOpId]
			if ok {
				sourceOpErr = err
			} else {
				destOpErr = err
			}
		}

		if destOpErr != nil {
			continue
		}

		if sourceOpErr != nil {
			return sourceOpErr
		}

		if destResource == "" {
			op, err := migration.MetadataAsOperation()
			if err != nil {
				return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
			}

			containers, ok := op.Resources["containers"]
			if !ok || len(containers) == 0 {
				return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
			}

			fields := strings.Split(containers[0], "/")
			fmt.Printf(i18n.G("Container name is: %s")+"\n", fields[len(fields)-1])
		}

		return nil
	}

	// Check for an error at the source
	sourceOp, sourceErr := source.GetOperation(sourceWSResponse.Operation)
	if sourceErr == nil && sourceOp.Err != "" {
		return fmt.Errorf(i18n.G("Migration failed on source host: %s"), sourceOp.Err)
	}

	// Return the error from destination
	return fmt.Errorf(i18n.G("Migration failed on target host: %s"), migrationErrFromClient)
}

func (c *copyCmd) run(conf *config.Config, args []string) error {
	if len(args) < 1 {
		return errArgs
	}

	ephem := 0
	if c.ephem {
		ephem = 1
	}

	if len(args) < 2 {
		return c.copyContainer(conf, args[0], "", false, ephem, false, c.containerOnly)
	}

	return c.copyContainer(conf, args[0], args[1], false, ephem, false, c.containerOnly)
}
<commit_msg>lxc/copy: Port to new client library<commit_after>package main

import (
	"fmt"
	"strings"

	"github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxc/config"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/gnuflag"
	"github.com/lxc/lxd/shared/i18n"
)

type copyCmd struct {
	profArgs      profileList
	confArgs      configList
	ephem         bool
	containerOnly bool
}

func (c *copyCmd) showByDefault() bool {
	return true
}

func (c *copyCmd) usage() string {
	return i18n.G(
		`Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] [--ephemeral|e] [--profile|-p <profile>...] [--config|-c <key=value>...] [--container-only]

Copy containers within or in between LXD instances.`)
}

func (c *copyCmd) flags() {
	gnuflag.Var(&c.confArgs, "config", i18n.G("Config key/value to apply to the new container"))
	gnuflag.Var(&c.confArgs, "c", i18n.G("Config key/value to apply to the new container"))
	gnuflag.Var(&c.profArgs, "profile", i18n.G("Profile to apply to the new container"))
	gnuflag.Var(&c.profArgs, "p", i18n.G("Profile to apply to the new container"))
	gnuflag.BoolVar(&c.ephem, "ephemeral", false, i18n.G("Ephemeral container"))
	gnuflag.BoolVar(&c.ephem, "e", false, i18n.G("Ephemeral container"))
	gnuflag.BoolVar(&c.containerOnly, "container-only", false, i18n.G("Copy the container without its snapshots"))
}

func (c *copyCmd) copyContainer(conf *config.Config, sourceResource string, destResource string, keepVolatile bool, ephemeral int, stateful bool, containerOnly bool) error {
	// Parse the source
	sourceRemote, sourceName, err := conf.ParseRemote(sourceResource)
	if err != nil {
		return err
	}

	// Parse the destination
	destRemote, destName, err := conf.ParseRemote(destResource)
	if err != nil {
		return err
	}

	// Make sure we have a container or snapshot name
	if sourceName == "" {
		return fmt.Errorf(i18n.G("You must specify a source container name"))
	}

	// If no destination name was provided, use the same as the source
	if destName == "" && destResource != "" {
		destName = sourceName
	}

	// Connect to the source host
	source, err := conf.GetContainerServer(sourceRemote)
	if err != nil {
		return err
	}

	// Connect to the destination host
	var dest lxd.ContainerServer
	if sourceRemote == destRemote {
		// Source and destination are the same
		dest = source
	} else {
		// Destination is different, connect to it
		dest, err = conf.GetContainerServer(destRemote)
		if err != nil {
			return err
		}
	}

	var op *lxd.RemoteOperation
	if shared.IsSnapshot(sourceName) {
		// Prepare the container creation request
		args := lxd.ContainerSnapshotCopyArgs{
			Name: destName,
		}

		// Copy of a snapshot into a new container
		srcFields := strings.SplitN(sourceName, shared.SnapshotDelimiter, 2)
		entry, _, err := source.GetContainerSnapshot(srcFields[0], srcFields[1])
		if err != nil {
			return err
		}

		// Allow adding additional profiles
		if c.profArgs != nil {
			entry.Profiles = append(entry.Profiles, c.profArgs...)
		}

		// Allow setting additional config keys
		if configMap != nil {
			for key, value := range configMap {
				entry.Config[key] = value
			}
		}

		// Allow overriding the ephemeral status
		if ephemeral == 1 {
			entry.Ephemeral = true
		} else if ephemeral == 0 {
			entry.Ephemeral = false
		}

		// Strip the volatile keys if requested
		if !keepVolatile {
			for k := range entry.Config {
				if k == "volatile.base_image" {
					continue
				}

				if strings.HasPrefix(k, "volatile") {
					delete(entry.Config, k)
				}
			}
		}

		// Do the actual copy
		op, err = dest.CopyContainerSnapshot(source, *entry, &args)
		if err != nil {
			return err
		}
	} else {
		// Prepare the container creation request
		args := lxd.ContainerCopyArgs{
			Name:          destName,
			Live:          stateful,
			ContainerOnly: containerOnly,
		}

		// Copy of a container into a new container
		entry, _, err := source.GetContainer(sourceName)
		if err != nil {
			return err
		}

		// Allow adding additional profiles
		if c.profArgs != nil {
			entry.Profiles = append(entry.Profiles, c.profArgs...)
		}

		// Allow setting additional config keys
		if configMap != nil {
			for key, value := range configMap {
				entry.Config[key] = value
			}
		}

		// Allow overriding the ephemeral status
		if ephemeral == 1 {
			entry.Ephemeral = true
		} else if ephemeral == 0 {
			entry.Ephemeral = false
		}

		// Strip the volatile keys if requested
		if !keepVolatile {
			for k := range entry.Config {
				if k == "volatile.base_image" {
					continue
				}

				if strings.HasPrefix(k, "volatile") {
					delete(entry.Config, k)
				}
			}
		}

		// Do the actual copy
		op, err = dest.CopyContainer(source, *entry, &args)
		if err != nil {
			return err
		}
	}

	// Wait for the copy to complete
	err = op.Wait()
	if err != nil {
		return err
	}

	// If choosing a random name, show it to the user
	if destResource == "" {
		// Get the succesful operation data
		opInfo, err := op.GetTarget()
		if err != nil {
			return err
		}

		// Extract the list of affected containers
		containers, ok := opInfo.Resources["containers"]
		if !ok || len(containers) != 1 {
			return fmt.Errorf(i18n.G("Failed to get the new container name"))
		}

		// Extract the name of the container
		fields := strings.Split(containers[0], "/")
		fmt.Printf(i18n.G("Container name is: %s")+"\n", fields[len(fields)-1])
	}

	return nil
}

func (c *copyCmd) run(conf *config.Config, args []string) error {
	// We at least need a source container name
	if len(args) < 1 {
		return errArgs
	}

	// For copies, default to non-ephemeral and allow override (move uses -1)
	ephem := 0
	if c.ephem {
		ephem = 1
	}

	// If not target name is specified, one will be chosed by the server
	if len(args) < 2 {
		return c.copyContainer(conf, args[0], "", false, ephem, false, c.containerOnly)
	}

	// Normal copy with a pre-determined name
	return c.copyContainer(conf, args[0], args[1], false, ephem, false, c.containerOnly)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"

	"github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxc/config"
	"github.com/lxc/lxd/shared"
	cli "github.com/lxc/lxd/shared/cmd"
	"github.com/lxc/lxd/shared/i18n"
)

type cmdInfo struct {
	global *cmdGlobal

	flagShowLog   bool
	flagResources bool
}

func (c *cmdInfo) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("info [<remote>:][<container>]")
	cmd.Short = i18n.G("Show container or server information")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Show container or server information`))
	cmd.Example = cli.FormatSection("", i18n.G(
		`lxc info [<remote>:]<container> [--show-log]
    For container information.

lxc info [<remote>:] [--resources]
    For LXD server information.`))

	cmd.RunE = c.Run
	cmd.Flags().BoolVar(&c.flagShowLog, "show-log", false, i18n.G("Show the container's last 100 log lines?"))
	cmd.Flags().BoolVar(&c.flagResources, "resources", false, i18n.G("Show the resources available to the server"))

	return cmd
}

func (c *cmdInfo) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Sanity checks
	exit, err := c.global.CheckArgs(cmd, args, 0, 1)
	if exit {
		return err
	}

	var remote string
	var cName string
	if len(args) == 1 {
		remote, cName, err = conf.ParseRemote(args[0])
		if err != nil {
			return err
		}
	} else {
		remote, cName, err = conf.ParseRemote("")
		if err != nil {
			return err
		}
	}

	d, err := conf.GetContainerServer(remote)
	if err != nil {
		return err
	}

	if cName == "" {
		return c.remoteInfo(d)
	}

	return c.containerInfo(d, conf.Remotes[remote], cName, c.flagShowLog)
}

func (c *cmdInfo) remoteInfo(d lxd.ContainerServer) error {
	if c.flagResources {
		resources, err := d.GetServerResources()
		if err != nil {
			return err
		}

		resourceData, err := yaml.Marshal(&resources)
		if err != nil {
			return err
		}

		fmt.Printf("%s", resourceData)

		return nil
	}

	serverStatus, _, err := d.GetServer()
	if err != nil {
		return err
	}

	data, err := yaml.Marshal(&serverStatus)
	if err != nil {
		return err
	}

	fmt.Printf("%s", data)

	return nil
}

func (c *cmdInfo) containerInfo(d lxd.ContainerServer, remote config.Remote, name string, showLog bool) error {
	ct, _, err := d.GetContainer(name)
	if err != nil {
		return err
	}

	cs, _, err := d.GetContainerState(name)
	if err != nil {
		return err
	}

	const layout = "2006/01/02 15:04 UTC"

	fmt.Printf(i18n.G("Name: %s")+"\n", ct.Name)
	if ct.Location != "" {
		fmt.Printf(i18n.G("Location: %s")+"\n", ct.Location)
	}
	if remote.Addr != "" {
		fmt.Printf(i18n.G("Remote: %s")+"\n", remote.Addr)
	}

	fmt.Printf(i18n.G("Architecture: %s")+"\n", ct.Architecture)
	if shared.TimeIsSet(ct.CreatedAt) {
		fmt.Printf(i18n.G("Created: %s")+"\n", ct.CreatedAt.UTC().Format(layout))
	}

	fmt.Printf(i18n.G("Status: %s")+"\n", ct.Status)
	if ct.Ephemeral {
		fmt.Printf(i18n.G("Type: ephemeral") + "\n")
	} else {
		fmt.Printf(i18n.G("Type: persistent") + "\n")
	}
	fmt.Printf(i18n.G("Profiles: %s")+"\n", strings.Join(ct.Profiles, ", "))
	if cs.Pid != 0 {
		fmt.Printf(i18n.G("Pid: %d")+"\n", cs.Pid)

		// IP addresses
		ipInfo := ""
		if cs.Network != nil {
			for netName, net := range cs.Network {
				vethStr := ""
				if net.HostName != "" {
					vethStr = fmt.Sprintf("\t%s", net.HostName)
				}

				for _, addr := range net.Addresses {
					ipInfo += fmt.Sprintf("  %s:\t%s\t%s%s\n", netName, addr.Family, addr.Address, vethStr)
				}
			}
		}

		if ipInfo != "" {
			fmt.Println(i18n.G("Ips:"))
			fmt.Printf(ipInfo)
		}
		fmt.Println(i18n.G("Resources:"))

		// Processes
		fmt.Printf("  "+i18n.G("Processes: %d")+"\n", cs.Processes)

		// Disk usage
		diskInfo := ""
		if cs.Disk != nil {
			for entry, disk := range cs.Disk {
				if disk.Usage != 0 {
					diskInfo += fmt.Sprintf("    %s: %s\n", entry, shared.GetByteSizeString(disk.Usage, 2))
				}
			}
		}

		if diskInfo != "" {
			fmt.Println(fmt.Sprintf("  %s", i18n.G("Disk usage:")))
			fmt.Printf(diskInfo)
		}

		// CPU usage
		cpuInfo := ""
		if cs.CPU.Usage != 0 {
			cpuInfo += fmt.Sprintf("    %s: %v\n", i18n.G("CPU usage (in seconds)"), cs.CPU.Usage/1000000000)
		}

		if cpuInfo != "" {
			fmt.Println(fmt.Sprintf("  %s", i18n.G("CPU usage:")))
			fmt.Printf(cpuInfo)
		}

		// Memory usage
		memoryInfo := ""
		if cs.Memory.Usage != 0 {
			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Memory (current)"), shared.GetByteSizeString(cs.Memory.Usage, 2))
		}

		if cs.Memory.UsagePeak != 0 {
			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Memory (peak)"), shared.GetByteSizeString(cs.Memory.UsagePeak, 2))
		}

		if cs.Memory.SwapUsage != 0 {
			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Swap (current)"), shared.GetByteSizeString(cs.Memory.SwapUsage, 2))
		}

		if cs.Memory.SwapUsagePeak != 0 {
			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Swap (peak)"), shared.GetByteSizeString(cs.Memory.SwapUsagePeak, 2))
		}

		if memoryInfo != "" {
			fmt.Println(fmt.Sprintf("  %s", i18n.G("Memory usage:")))
			fmt.Printf(memoryInfo)
		}

		// Network usage
		networkInfo := ""
		if cs.Network != nil {
			for netName, net := range cs.Network {
				networkInfo += fmt.Sprintf("    %s:\n", netName)
				networkInfo += fmt.Sprintf("      %s: %s\n", i18n.G("Bytes received"), shared.GetByteSizeString(net.Counters.BytesReceived, 2))
				networkInfo += fmt.Sprintf("      %s: %s\n", i18n.G("Bytes sent"), shared.GetByteSizeString(net.Counters.BytesSent, 2))
				networkInfo += fmt.Sprintf("      %s: %d\n", i18n.G("Packets received"), net.Counters.PacketsReceived)
				networkInfo += fmt.Sprintf("      %s: %d\n", i18n.G("Packets sent"), net.Counters.PacketsSent)
			}
		}

		if networkInfo != "" {
			fmt.Println(fmt.Sprintf("  %s", i18n.G("Network usage:")))
			fmt.Printf(networkInfo)
		}
	}

	// List snapshots
	firstSnapshot := true
	snaps, err := d.GetContainerSnapshots(name)
	if err != nil {
		return nil
	}

	for _, snap := range snaps {
		if firstSnapshot {
			fmt.Println(i18n.G("Snapshots:"))
		}

		fields := strings.Split(snap.Name, shared.SnapshotDelimiter)
		fmt.Printf("  %s", fields[len(fields)-1])

		if shared.TimeIsSet(snap.CreatedAt) {
			fmt.Printf(" ("+i18n.G("taken at %s")+")", snap.CreatedAt.UTC().Format(layout))
		}

		if shared.TimeIsSet(snap.ExpiresAt) {
			fmt.Printf(" ("+i18n.G("expires at %s")+")", snap.ExpiresAt.UTC().Format(layout))
		}

		if snap.Stateful {
			fmt.Printf(" (" + i18n.G("stateful") + ")")
		} else {
			fmt.Printf(" (" + i18n.G("stateless") + ")")
		}
		fmt.Printf("\n")

		firstSnapshot = false
	}

	if showLog {
		log, err := d.GetContainerLogfile(name, "lxc.log")
		if err != nil {
			return err
		}

		stuff, err := ioutil.ReadAll(log)
		if err != nil {
			return err
		}

		fmt.Printf("\n"+i18n.G("Log:")+"\n\n%s\n", string(stuff))
	}

	return nil
}
<commit_msg>lxc/info: Add targeting to 'lxc info'<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"

	"github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxc/config"
	"github.com/lxc/lxd/shared"
	cli "github.com/lxc/lxd/shared/cmd"
	"github.com/lxc/lxd/shared/i18n"
)

type cmdInfo struct {
	global *cmdGlobal

	flagShowLog   bool
	flagResources bool
	flagTarget    string
}

func (c *cmdInfo) Command() *cobra.Command {
	cmd := &cobra.Command{}
	cmd.Use = i18n.G("info [<remote>:][<container>]")
	cmd.Short = i18n.G("Show container or server information")
	cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
		`Show container or server information`))
	cmd.Example = cli.FormatSection("", i18n.G(
		`lxc info [<remote>:]<container> [--show-log]
    For container information.

lxc info [<remote>:] [--resources]
    For LXD server information.`))

	cmd.RunE = c.Run
	cmd.Flags().BoolVar(&c.flagShowLog, "show-log", false, i18n.G("Show the container's last 100 log lines?"))
	cmd.Flags().BoolVar(&c.flagResources, "resources", false, i18n.G("Show the resources available to the server"))
	cmd.Flags().StringVar(&c.flagTarget, "target", "", i18n.G("Cluster member name")+"``")

	return cmd
}

func (c *cmdInfo) Run(cmd *cobra.Command, args []string) error {
	conf := c.global.conf

	// Sanity checks
	exit, err := c.global.CheckArgs(cmd, args, 0, 1)
	if exit {
		return err
	}

	var remote string
	var cName string
	if len(args) == 1 {
		remote, cName, err = conf.ParseRemote(args[0])
		if err != nil {
			return err
		}
	} else {
		remote, cName, err = conf.ParseRemote("")
		if err != nil {
			return err
		}
	}

	d, err := conf.GetContainerServer(remote)
	if err != nil {
		return err
	}

	if cName == "" {
		return c.remoteInfo(d)
	}

	return c.containerInfo(d, conf.Remotes[remote], cName, c.flagShowLog)
}

func (c *cmdInfo) remoteInfo(d lxd.ContainerServer) error {
	// Targeting
	if c.flagTarget != "" {
		if !d.IsClustered() {
			return fmt.Errorf(i18n.G("To use --target, the destination remote must be a cluster"))
		}

		d = d.UseTarget(c.flagTarget)
	}

	if c.flagResources {
		resources, err := d.GetServerResources()
		if err != nil {
			return err
		}

		resourceData, err := yaml.Marshal(&resources)
		if err != nil {
			return err
		}

		fmt.Printf("%s", resourceData)

		return nil
	}

	serverStatus, _, err := d.GetServer()
	if err != nil {
		return err
	}

	data, err := yaml.Marshal(&serverStatus)
	if err != nil {
		return err
	}

	fmt.Printf("%s", data)

	return nil
}

func (c *cmdInfo) containerInfo(d lxd.ContainerServer, remote config.Remote, name string, showLog bool) error {
	// Sanity checks
	if c.flagTarget != "" {
		return fmt.Errorf(i18n.G("--target cannot be used with containers"))
	}

	ct, _, err := d.GetContainer(name)
	if err != nil {
		return err
	}

	cs, _, err := d.GetContainerState(name)
	if err != nil {
		return err
	}

	const layout = "2006/01/02 15:04 UTC"

	fmt.Printf(i18n.G("Name: %s")+"\n", ct.Name)
	if ct.Location != "" {
		fmt.Printf(i18n.G("Location: %s")+"\n", ct.Location)
	}
	if remote.Addr != "" {
		fmt.Printf(i18n.G("Remote: %s")+"\n", remote.Addr)
	}

	fmt.Printf(i18n.G("Architecture: %s")+"\n", ct.Architecture)
	if shared.TimeIsSet(ct.CreatedAt) {
		fmt.Printf(i18n.G("Created: %s")+"\n", ct.CreatedAt.UTC().Format(layout))
	}

	fmt.Printf(i18n.G("Status: %s")+"\n", ct.Status)
	if ct.Ephemeral {
		fmt.Printf(i18n.G("Type: ephemeral") + "\n")
	} else {
		fmt.Printf(i18n.G("Type: persistent") + "\n")
	}
	fmt.Printf(i18n.G("Profiles: %s")+"\n", strings.Join(ct.Profiles, ", "))
	if cs.Pid != 0 {
		fmt.Printf(i18n.G("Pid: %d")+"\n", cs.Pid)

		// IP addresses
		ipInfo := ""
		if cs.Network != nil {
			for netName, net := range cs.Network {
				vethStr := ""
				if net.HostName != "" {
					vethStr = fmt.Sprintf("\t%s", net.HostName)
				}

				for _, addr := range net.Addresses {
					ipInfo += fmt.Sprintf("  %s:\t%s\t%s%s\n", netName, addr.Family, addr.Address, vethStr)
				}
			}
		}

		if ipInfo != "" {
			fmt.Println(i18n.G("Ips:"))
			fmt.Printf(ipInfo)
		}
		fmt.Println(i18n.G("Resources:"))

		// Processes
		fmt.Printf("  "+i18n.G("Processes: %d")+"\n", cs.Processes)

		// Disk usage
		diskInfo := ""
		if cs.Disk != nil {
			for entry, disk := range cs.Disk {
				if disk.Usage != 0 {
					diskInfo += fmt.Sprintf("    %s: %s\n", entry, shared.GetByteSizeString(disk.Usage, 2))
				}
			}
		}

		if diskInfo != "" {
			fmt.Println(fmt.Sprintf("  %s", i18n.G("Disk usage:")))
			fmt.Printf(diskInfo)
		}

		// CPU usage
		cpuInfo := ""
		if cs.CPU.Usage != 0 {
			cpuInfo += fmt.Sprintf("    %s: %v\n", i18n.G("CPU usage (in seconds)"), cs.CPU.Usage/1000000000)
		}

		if cpuInfo != "" {
			fmt.Println(fmt.Sprintf("  %s", i18n.G("CPU usage:")))
			fmt.Printf(cpuInfo)
		}

		// Memory usage
		memoryInfo := ""
		if cs.Memory.Usage != 0 {
			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Memory (current)"), shared.GetByteSizeString(cs.Memory.Usage, 2))
		}

		if cs.Memory.UsagePeak != 0 {
			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Memory (peak)"), shared.GetByteSizeString(cs.Memory.UsagePeak, 2))
		}

		if cs.Memory.SwapUsage != 0 {
			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Swap (current)"), shared.GetByteSizeString(cs.Memory.SwapUsage, 2))
		}

		if cs.Memory.SwapUsagePeak != 0 {
			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Swap (peak)"), shared.GetByteSizeString(cs.Memory.SwapUsagePeak, 2))
		}

		if memoryInfo != "" {
			fmt.Println(fmt.Sprintf("  %s", i18n.G("Memory usage:")))
			fmt.Printf(memoryInfo)
		}

		// Network usage
		networkInfo := ""
		if cs.Network != nil {
			for netName, net := range cs.Network {
				networkInfo += fmt.Sprintf("    %s:\n", netName)
				networkInfo += fmt.Sprintf("      %s: %s\n", i18n.G("Bytes received"), shared.GetByteSizeString(net.Counters.BytesReceived, 2))
				networkInfo += fmt.Sprintf("      %s: %s\n", i18n.G("Bytes sent"), shared.GetByteSizeString(net.Counters.BytesSent, 2))
				networkInfo += fmt.Sprintf("      %s: %d\n", i18n.G("Packets received"), net.Counters.PacketsReceived)
				networkInfo += fmt.Sprintf("      %s: %d\n", i18n.G("Packets sent"), net.Counters.PacketsSent)
			}
		}

		if networkInfo != "" {
			fmt.Println(fmt.Sprintf("  %s", i18n.G("Network usage:")))
			fmt.Printf(networkInfo)
		}
	}

	// List snapshots
	firstSnapshot := true
	snaps, err := d.GetContainerSnapshots(name)
	if err != nil {
		return nil
	}

	for _, snap := range snaps {
		if firstSnapshot {
			fmt.Println(i18n.G("Snapshots:"))
		}

		fields := strings.Split(snap.Name, shared.SnapshotDelimiter)
		fmt.Printf("  %s", fields[len(fields)-1])

		if shared.TimeIsSet(snap.CreatedAt) {
			fmt.Printf(" ("+i18n.G("taken at %s")+")", snap.CreatedAt.UTC().Format(layout))
		}

		if shared.TimeIsSet(snap.ExpiresAt) {
			fmt.Printf(" ("+i18n.G("expires at %s")+")", snap.ExpiresAt.UTC().Format(layout))
		}

		if snap.Stateful {
			fmt.Printf(" (" + i18n.G("stateful") + ")")
		} else {
			fmt.Printf(" (" + i18n.G("stateless") + ")")
		}
		fmt.Printf("\n")

		firstSnapshot = false
	}

	if showLog {
		log, err := d.GetContainerLogfile(name, "lxc.log")
		if err != nil {
			return err
		}

		stuff, err := ioutil.ReadAll(log)
		if err != nil {
			return err
		}

		fmt.Printf("\n"+i18n.G("Log:")+"\n\n%s\n", string(stuff))
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"

	"github.com/lxc/lxd/lxc/config"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/gnuflag"
	"github.com/lxc/lxd/shared/i18n"
	"github.com/pkg/errors"
)

type moveCmd struct {
	containerOnly bool
	mode          string
	stateless     bool
	target        string
}

func (c *moveCmd) showByDefault() bool {
	return true
}

func (c *moveCmd) usage() string {
	return i18n.G(
		`Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/<snapshot>]] [--container-only] [--target <node>]

Move containers within or in between LXD instances.

lxc move [<remote>:]<source container> [<remote>:][<destination container>] [--container-only]
    Move a container between two hosts, renaming it if destination name differs.

lxc move <old name> <new name> [--container-only]
    Rename a local container.

lxc move <container>/<old snapshot name> <container>/<new snapshot name>
    Rename a snapshot.`)
}

func (c *moveCmd) flags() {
	gnuflag.BoolVar(&c.containerOnly, "container-only", false, i18n.G("Move the container without its snapshots"))
	gnuflag.StringVar(&c.mode, "mode", "pull", i18n.G("Transfer mode. One of pull (default), push or relay."))
	gnuflag.BoolVar(&c.stateless, "stateless", false, i18n.G("Copy a stateful container stateless"))
	gnuflag.StringVar(&c.target, "target", "", i18n.G("Node name"))
}

func (c *moveCmd) run(conf *config.Config, args []string) error {
	if len(args) != 2 && c.target == "" {
		return errArgs
	}

	// Parse the mode
	mode := "pull"
	if c.mode != "" {
		mode = c.mode
	}

	sourceRemote, sourceName, err := conf.ParseRemote(args[0])
	if err != nil {
		return err
	}

	destRemote := sourceRemote
	destName := ""
	if len(args) == 2 {
		var err error
		destRemote, destName, err = conf.ParseRemote(args[1])
		if err != nil {
			return err
		}
	}

	// Target node and destination remote can't be used together.
	if c.target != "" && sourceRemote != destRemote {
		return fmt.Errorf(i18n.G("You must use the same source and destination remote when using --target"))
	}

	// As an optimization, if the source an destination are the same, do
	// this via a simple rename. This only works for containers that aren't
	// running, containers that are running should be live migrated (of
	// course, this changing of hostname isn't supported right now, so this
	// simply won't work).
	if sourceRemote == destRemote && c.target == "" {
		source, err := conf.GetContainerServer(sourceRemote)
		if err != nil {
			return err
		}

		if shared.IsSnapshot(sourceName) {
			// Snapshot rename
			srcFields := strings.SplitN(sourceName, shared.SnapshotDelimiter, 2)
			dstFields := strings.SplitN(destName, shared.SnapshotDelimiter, 2)

			op, err := source.RenameContainerSnapshot(srcFields[0], srcFields[1], api.ContainerSnapshotPost{Name: dstFields[1]})
			if err != nil {
				return err
			}

			return op.Wait()
		}

		// Container rename
		op, err := source.RenameContainer(sourceName, api.ContainerPost{Name: destName})
		if err != nil {
			return err
		}

		return op.Wait()
	}

	sourceResource := args[0]
	destResource := sourceResource
	if len(args) == 2 {
		destResource = args[1]
	}

	// If the target option was specified, we're moving a container from a
	// cluster node to another. In case the rootfs of the container is
	// backed by ceph, we want to re-use the same ceph volume. This assumes
	// that the container is not running.
	if c.target != "" {
		moved, err := maybeMoveCephContaner(conf, sourceResource, destResource, c.target)
		if err != nil {
			return err
		}
		if moved {
			return nil
		}
	}

	cpy := copyCmd{}
	cpy.target = c.target

	stateful := !c.stateless

	// A move is just a copy followed by a delete; however, we want to
	// keep the volatile entries around since we are moving the container.
	err = cpy.copyContainer(conf, sourceResource, destResource, true, -1, stateful, c.containerOnly, mode)
	if err != nil {
		return err
	}

	del := deleteCmd{}
	del.force = true
	return del.run(conf, args[:1])
}

// Helper to check if the container to be moved is backed by a ceph storage
// pool, and use the special POST /containers/<name>?target=<node> API if so.
//
// It returns false if the container is not backed by ceph, true otherwise.
func maybeMoveCephContaner(conf *config.Config, sourceResource, destResource, target string) (bool, error) {
	// Parse the source.
	sourceRemote, sourceName, err := conf.ParseRemote(sourceResource)
	if err != nil {
		return false, err
	}

	// Parse the destination.
	destRemote, destName, err := conf.ParseRemote(destResource)
	if err != nil {
		return false, err
	}

	if sourceRemote != destRemote {
		return false, fmt.Errorf(
			i18n.G("You must use the same source and destination remote when using --target"))
	}

	// Make sure we have a container or snapshot name.
	if sourceName == "" {
		return false, fmt.Errorf(i18n.G("You must specify a source container name"))
	}

	// The destination name is optional.
	if destName == "" {
		destName = sourceName
	}

	// Connect to the source host
	source, err := conf.GetContainerServer(sourceRemote)
	if err != nil {
		return false, err
	}

	if shared.IsSnapshot(sourceName) {
		// TODO: implement moving snapshots.
		return false, fmt.Errorf("Moving ceph snapshots between cluster nodes is not yet implemented")
	}

	// Check if the container to be moved is backed by ceph.
	container, _, err := source.GetContainer(sourceName)
	if err != nil {
		// If we are unable to connect, we assume that the source node
		// is offline, and we'll try to perform the migration. If the
		// container turns out to not be backed by ceph, the migrate
		// API will still return an error.
		if !strings.Contains(err.Error(), "Unable to connect") {
			return false, errors.Wrapf(err, "Failed to get container %s", sourceName)
		}
	}
	if container != nil {
		devices := container.Devices
		for key, value := range container.ExpandedDevices {
			devices[key] = value
		}
		_, device, err := shared.GetRootDiskDevice(devices)
		if err != nil {
			return false, errors.Wrapf(err, "Failed parse root disk device")
		}

		poolName, ok := device["pool"]
		if !ok {
			return false, nil
		}

		pool, _, err := source.GetStoragePool(poolName)
		if err != nil {
			return false, errors.Wrapf(err, "Failed get root disk device pool %s", poolName)
		}
		if pool.Driver != "ceph" {
			return false, nil
		}
	}

	// The migrate API will do the right thing when passed a target.
	source = source.UseTarget(target)
	req := api.ContainerPost{Name: destName, Migration: true}
	op, err := source.MigrateContainer(sourceName, req)
	if err != nil {
		return false, err
	}
	err = op.Wait()
	if err != nil {
		return false, err
	}
	return true, nil
}
<commit_msg>Fix typo<commit_after>package main

import (
	"fmt"
	"strings"

	"github.com/lxc/lxd/lxc/config"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/gnuflag"
	"github.com/lxc/lxd/shared/i18n"
	"github.com/pkg/errors"
)

type moveCmd struct {
	containerOnly bool
	mode          string
	stateless     bool
	target        string
}

func (c *moveCmd) showByDefault() bool {
	return true
}

func (c *moveCmd) usage() string {
	return i18n.G(
		`Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/<snapshot>]] [--container-only] [--target <node>]

Move containers within or in between LXD instances.

lxc move [<remote>:]<source container> [<remote>:][<destination container>] [--container-only]
    Move a container between two hosts, renaming it if destination name differs.

lxc move <old name> <new name> [--container-only]
    Rename a local container.

lxc move <container>/<old snapshot name> <container>/<new snapshot name>
    Rename a snapshot.`)
}

func (c *moveCmd) flags() {
	gnuflag.BoolVar(&c.containerOnly, "container-only", false, i18n.G("Move the container without its snapshots"))
	gnuflag.StringVar(&c.mode, "mode", "pull", i18n.G("Transfer mode. One of pull (default), push or relay."))
	gnuflag.BoolVar(&c.stateless, "stateless", false, i18n.G("Copy a stateful container stateless"))
	gnuflag.StringVar(&c.target, "target", "", i18n.G("Node name"))
}

func (c *moveCmd) run(conf *config.Config, args []string) error {
	if len(args) != 2 && c.target == "" {
		return errArgs
	}

	// Parse the mode
	mode := "pull"
	if c.mode != "" {
		mode = c.mode
	}

	sourceRemote, sourceName, err := conf.ParseRemote(args[0])
	if err != nil {
		return err
	}

	destRemote := sourceRemote
	destName := ""
	if len(args) == 2 {
		var err error
		destRemote, destName, err = conf.ParseRemote(args[1])
		if err != nil {
			return err
		}
	}

	// Target node and destination remote can't be used together.
	if c.target != "" && sourceRemote != destRemote {
		return fmt.Errorf(i18n.G("You must use the same source and destination remote when using --target"))
	}

	// As an optimization, if the source an destination are the same, do
	// this via a simple rename. This only works for containers that aren't
	// running, containers that are running should be live migrated (of
	// course, this changing of hostname isn't supported right now, so this
	// simply won't work).
	if sourceRemote == destRemote && c.target == "" {
		source, err := conf.GetContainerServer(sourceRemote)
		if err != nil {
			return err
		}

		if shared.IsSnapshot(sourceName) {
			// Snapshot rename
			srcFields := strings.SplitN(sourceName, shared.SnapshotDelimiter, 2)
			dstFields := strings.SplitN(destName, shared.SnapshotDelimiter, 2)

			op, err := source.RenameContainerSnapshot(srcFields[0], srcFields[1], api.ContainerSnapshotPost{Name: dstFields[1]})
			if err != nil {
				return err
			}

			return op.Wait()
		}

		// Container rename
		op, err := source.RenameContainer(sourceName, api.ContainerPost{Name: destName})
		if err != nil {
			return err
		}

		return op.Wait()
	}

	sourceResource := args[0]
	destResource := sourceResource
	if len(args) == 2 {
		destResource = args[1]
	}

	// If the target option was specified, we're moving a container from a
	// cluster node to another. In case the rootfs of the container is
	// backed by ceph, we want to re-use the same ceph volume. This assumes
	// that the container is not running.
	if c.target != "" {
		moved, err := maybeMoveCephContainer(conf, sourceResource, destResource, c.target)
		if err != nil {
			return err
		}
		if moved {
			return nil
		}
	}

	cpy := copyCmd{}
	cpy.target = c.target

	stateful := !c.stateless

	// A move is just a copy followed by a delete; however, we want to
	// keep the volatile entries around since we are moving the container.
	err = cpy.copyContainer(conf, sourceResource, destResource, true, -1, stateful, c.containerOnly, mode)
	if err != nil {
		return err
	}

	del := deleteCmd{}
	del.force = true
	return del.run(conf, args[:1])
}

// Helper to check if the container to be moved is backed by a ceph storage
// pool, and use the special POST /containers/<name>?target=<node> API if so.
//
// It returns false if the container is not backed by ceph, true otherwise.
func maybeMoveCephContainer(conf *config.Config, sourceResource, destResource, target string) (bool, error) {
	// Parse the source.
	sourceRemote, sourceName, err := conf.ParseRemote(sourceResource)
	if err != nil {
		return false, err
	}

	// Parse the destination.
	destRemote, destName, err := conf.ParseRemote(destResource)
	if err != nil {
		return false, err
	}

	if sourceRemote != destRemote {
		return false, fmt.Errorf(
			i18n.G("You must use the same source and destination remote when using --target"))
	}

	// Make sure we have a container or snapshot name.
	if sourceName == "" {
		return false, fmt.Errorf(i18n.G("You must specify a source container name"))
	}

	// The destination name is optional.
	if destName == "" {
		destName = sourceName
	}

	// Connect to the source host
	source, err := conf.GetContainerServer(sourceRemote)
	if err != nil {
		return false, err
	}

	if shared.IsSnapshot(sourceName) {
		// TODO: implement moving snapshots.
		return false, fmt.Errorf("Moving ceph snapshots between cluster nodes is not yet implemented")
	}

	// Check if the container to be moved is backed by ceph.
	container, _, err := source.GetContainer(sourceName)
	if err != nil {
		// If we are unable to connect, we assume that the source node
		// is offline, and we'll try to perform the migration. If the
		// container turns out to not be backed by ceph, the migrate
		// API will still return an error.
		if !strings.Contains(err.Error(), "Unable to connect") {
			return false, errors.Wrapf(err, "Failed to get container %s", sourceName)
		}
	}
	if container != nil {
		devices := container.Devices
		for key, value := range container.ExpandedDevices {
			devices[key] = value
		}
		_, device, err := shared.GetRootDiskDevice(devices)
		if err != nil {
			return false, errors.Wrapf(err, "Failed parse root disk device")
		}

		poolName, ok := device["pool"]
		if !ok {
			return false, nil
		}

		pool, _, err := source.GetStoragePool(poolName)
		if err != nil {
			return false, errors.Wrapf(err, "Failed get root disk device pool %s", poolName)
		}
		if pool.Driver != "ceph" {
			return false, nil
		}
	}

	// The migrate API will do the right thing when passed a target.
	source = source.UseTarget(target)
	req := api.ContainerPost{Name: destName, Migration: true}
	op, err := source.MigrateContainer(sourceName, req)
	if err != nil {
		return false, err
	}
	err = op.Wait()
	if err != nil {
		return false, err
	}
	return true, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"net/http"
	"net/http/httptest"
	"os"
	"regexp"
	"testing"
)

func TestPrintSites(t *testing.T) {
	siteResp := `[{"id": 1337, "name": "Dyer"}]`
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/sites.json" {
			t.Errorf("fetchRource hit wrong endpoint (wanted /sites.json but got %v)", r.URL.Path)
		}
		w.Write([]byte(siteResp))
	}))
	defer ts.Close()
	baseURL = ts.URL

	out = &bytes.Buffer{}
	defer func() {
		out = os.Stdout
	}()

	fetchResource("Sites")

	pattern := `\| +1337 +\| +Dyer +\|`
	matched, err := regexp.Match(pattern, out.(*bytes.Buffer).Bytes())
	if err != nil {
		t.Error("Error with pattern match:", err)
	}

	if !matched {
		t.Logf("Table didn't match properly:")
		t.Logf("%v\n", out)
		t.Errorf("should have matched %v", pattern)
	}
}

func TestPrintFolders(t *testing.T) {
	siteResp := `[{"id": 707, "title": "Foo"}]`
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/folders.json" {
			t.Errorf("fetchRource hit wrong endpoint (wanted /Folders.json but got %v)", r.URL.Path)
		}
		w.Write([]byte(siteResp))
	}))
	defer ts.Close()
	baseURL = ts.URL

	out = &bytes.Buffer{}
	defer func() {
		out = os.Stdout
	}()

	fetchResource("Folders")

	pattern := `\| +707 +\| +Foo +\|`
	matched, err := regexp.Match(pattern, out.(*bytes.Buffer).Bytes())
	if err != nil {
		t.Error("Error with pattern match:", err)
	}

	if !matched {
		t.Logf("Table didn't match properly:")
		t.Logf("%v\n", out)
		t.Errorf("should have matched %v", pattern)
	}
}

func TestBrowsersFolders(t *testing.T) {
	siteResp := `{"available_browsers": [{"name": "firefox", "description": "Mozilla Firefox"}]}`
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/clients.json" {
			t.Errorf("fetchRource hit wrong endpoint (wanted /clients.json but got %v)", r.URL.Path)
		}
		w.Write([]byte(siteResp))
	}))
	defer ts.Close()
	baseURL = ts.URL

	out = &bytes.Buffer{}
	defer func() {
		out = os.Stdout
	}()

	fetchResource("Browsers")

	pattern := `\| +firefox +\| +Mozilla Firefox +\|`
	matched, err := regexp.Match(pattern, out.(*bytes.Buffer).Bytes())
	if err != nil {
		t.Error("Error with pattern match:", err)
	}

	if !matched {
		t.Logf("Table didn't match properly:")
		t.Logf("%v\n", out)
		t.Errorf("should have matched %v", pattern)
	}
}
<commit_msg>modified integration test to work with refactor<commit_after>package main

import (
	"bytes"
	"net/http"
	"net/http/httptest"
	"os"
	"regexp"
	"testing"
)

func TestPrintSites(t *testing.T) {
	siteResp := `[{"id": 1337, "name": "Dyer"}]`
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/sites.json" {
			t.Errorf("fetchRource hit wrong endpoint (wanted /sites.json but got %v)", r.URL.Path)
		}
		w.Write([]byte(siteResp))
	}))
	defer ts.Close()
	baseURL = ts.URL

	out = &bytes.Buffer{}
	defer func() {
		out = os.Stdout
	}()

	printSites()

	pattern := `\| +1337 +\| +Dyer +\|`
	matched, err := regexp.Match(pattern, out.(*bytes.Buffer).Bytes())
	if err != nil {
		t.Error("Error with pattern match:", err)
	}

	if !matched {
		t.Logf("Table didn't match properly:")
		t.Logf("%v\n", out)
		t.Errorf("should have matched %v", pattern)
	}
}

func TestPrintFolders(t *testing.T) {
	siteResp := `[{"id": 707, "title": "Foo"}]`
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/folders.json" {
			t.Errorf("fetchRource hit wrong endpoint (wanted /Folders.json but got %v)", r.URL.Path)
		}
		w.Write([]byte(siteResp))
	}))
	defer ts.Close()
	baseURL = ts.URL

	out = &bytes.Buffer{}
	defer func() {
		out = os.Stdout
	}()

	printSites()

	pattern := `\| +707 +\| +Foo +\|`
	matched, err := regexp.Match(pattern, out.(*bytes.Buffer).Bytes())
	if err != nil {
		t.Error("Error with pattern match:", err)
	}

	if !matched {
		t.Logf("Table didn't match properly:")
		t.Logf("%v\n", out)
		t.Errorf("should have matched %v", pattern)
	}
}

func TestBrowsersFolders(t *testing.T) {
	siteResp := `{"available_browsers": [{"name": "firefox", "description": "Mozilla Firefox"}]}`
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/clients.json" {
			t.Errorf("fetchRource hit wrong endpoint (wanted /clients.json but got %v)", r.URL.Path)
		}
		w.Write([]byte(siteResp))
	}))
	defer ts.Close()
	baseURL = ts.URL

	out = &bytes.Buffer{}
	defer func() {
		out = os.Stdout
	}()

	printSites()

	pattern := `\| +firefox +\| +Mozilla Firefox +\|`
	matched, err := regexp.Match(pattern, out.(*bytes.Buffer).Bytes())
	if err != nil {
		t.Error("Error with pattern match:", err)
	}

	if !matched {
		t.Logf("Table didn't match properly:")
		t.Logf("%v\n", out)
		t.Errorf("should have matched %v", pattern)
	}
}
<|endoftext|>


<commit_before>package homecloud

import (
	"reflect"
	"sync"
	"time"

	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-ninja/logger"
	"github.com/ninjasphere/go-ninja/model"
	"github.com/ninjasphere/redigo/redis"
	"github.com/ninjasphere/sphere-go-homecloud/models"
)

var log = logger.GetLogger("HomeCloud")

var syncEnabled = config.Bool(true, "homecloud.sync.enabled")
var syncTimeout = config.MustDuration("homecloud.sync.timeout")

type HomeCloud struct {
	Conn         *ninja.Connection    `inject:""`
	Pool         *redis.Pool          `inject:""`
	ThingModel   *models.ThingModel   `inject:""`
	DeviceModel  *models.DeviceModel  `inject:""`
	ChannelModel *models.ChannelModel `inject:""`
	RoomModel    *models.RoomModel    `inject:""`
	ModuleModel  *models.ModuleModel  `inject:""`
	SiteModel    *models.SiteModel    `inject:""`
	log          *logger.Logger
}

func (c *HomeCloud) PostConstruct() error {

	c.log = logger.GetLogger("HomeCloud")

	c.ExportRPCServices()
	c.ensureSiteExists()

	ledController := c.Conn.GetServiceClient("$home/led-controller")
	err := ledController.Call("enableControl", nil, nil, 0)
	if err != nil {
		c.log.Infof("Failed to enable control on LED controller: %s", err)
	}

	if syncEnabled {
		// We wait for at least one sync to happen, or fail
		<-c.StartSyncing(config.MustDuration("homecloud.sync.interval"))
	}

	c.AutoStartModules()

	return nil
}

// if config.Bool(false, "clearcloud") {

func (c *HomeCloud) ClearCloud() {
	log.Infof("Clearing all cloud data in 5 seconds")

	time.Sleep(time.Second * 5)

	c.ThingModel.ClearCloud()
	c.ChannelModel.ClearCloud()
	c.DeviceModel.ClearCloud()
	c.RoomModel.ClearCloud()
	c.SiteModel.ClearCloud()

	log.Infof("All cloud data cleared? Probably.")
}

func (c *HomeCloud) ExportRPCServices() {
	c.Conn.MustExportService(c.ThingModel, "$home/services/ThingModel", &model.ServiceAnnouncement{
		Schema: "/service/thing-model",
	})
	c.Conn.MustExportService(c.DeviceModel, "$home/services/DeviceModel", &model.ServiceAnnouncement{
		Schema: "/service/device-model",
	})
	c.Conn.MustExportService(c.RoomModel, "$home/services/RoomModel", &model.ServiceAnnouncement{
		Schema: "/service/room-model",
	})
	c.Conn.MustExportService(c.SiteModel, "$home/services/SiteModel", &model.ServiceAnnouncement{
		Schema: "/service/site-model",
	})
}

type syncable interface {
	Sync(timeout time.Duration) error
}

func (c *HomeCloud) StartSyncing(interval time.Duration) chan bool {

	syncComplete := make(chan bool)

	syncModels := []syncable{c.RoomModel, c.DeviceModel, c.ChannelModel, c.ThingModel, c.ThingModel}

	go func() {
		for {

			c.log.Infof("\n\n\n------ Timed model syncing started (every %s) ------ ", interval.String())

			var wg sync.WaitGroup

			wg.Add(len(syncModels))

			success := true

			for _, model := range syncModels {
				go func(model syncable) {
					err := model.Sync(syncTimeout)
					if err != nil {
						c.log.Warningf("Failed to sync model %s : %s", reflect.TypeOf(model).String(), err)
						success = false
					}
					wg.Done()
				}(model)
			}

			wg.Wait()

			log.Infof("------ Timed model syncing complete. Success? %t ------\n\n\n", success)

			select {
			case syncComplete <- success:
			default:
			}

			time.Sleep(interval)
		}
	}()

	return syncComplete
}

func (c *HomeCloud) ensureSiteExists() {
	site, err := c.SiteModel.Fetch(config.MustString("siteId"))
	if err != nil && err != models.RecordNotFound {
		log.Fatalf("Failed to get site: %s", err)
	}

	if err == models.RecordNotFound {
		siteType := "home"
		name := "Home"
		site = &model.Site{
			Name: &name,
			ID:   config.MustString("siteId"),
			Type: &siteType,
		}
		err = c.SiteModel.Create(site)
		if err != nil && err != models.RecordNotFound {
			log.Fatalf("Failed to create site: %s", err)
		}
	}

}

func (c *HomeCloud) AutoStartModules() {

	do := func(name string, task string) error {
		return c.Conn.SendNotification("$node/"+config.Serial()+"/module/"+task, name)
	}

	interval := config.MustDuration("homecloud.autoStart.interval")

	for _, name := range config.MustStringArray("homecloud.autoStart.modules") {
		log.Infof("-- (Re)starting '%s'", name)

		err := do(name, "stop")
		if err != nil {
			log.Fatalf("Failed to send %s stop message! %s", name, err)
		}

		time.Sleep(interval)

		err = do(name, "start")
		if err != nil {
			log.Fatalf("Failed to send %s start message! %s", name, err)
		}
	}

}
<commit_msg>SD-108 Autostart apps on userdata partition for convenience.<commit_after>package homecloud

import (
	"path/filepath"
	"reflect"
	"sync"
	"time"

	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-ninja/logger"
	"github.com/ninjasphere/go-ninja/model"
	"github.com/ninjasphere/redigo/redis"
	"github.com/ninjasphere/sphere-go-homecloud/models"
)

var log = logger.GetLogger("HomeCloud")

var syncEnabled = config.Bool(true, "homecloud.sync.enabled")
var syncTimeout = config.MustDuration("homecloud.sync.timeout")

type HomeCloud struct {
	Conn         *ninja.Connection    `inject:""`
	Pool         *redis.Pool          `inject:""`
	ThingModel   *models.ThingModel   `inject:""`
	DeviceModel  *models.DeviceModel  `inject:""`
	ChannelModel *models.ChannelModel `inject:""`
	RoomModel    *models.RoomModel    `inject:""`
	ModuleModel  *models.ModuleModel  `inject:""`
	SiteModel    *models.SiteModel    `inject:""`
	log          *logger.Logger
}

func (c *HomeCloud) PostConstruct() error {

	c.log = logger.GetLogger("HomeCloud")

	c.ExportRPCServices()
	c.ensureSiteExists()

	ledController := c.Conn.GetServiceClient("$home/led-controller")
	err := ledController.Call("enableControl", nil, nil, 0)
	if err != nil {
		c.log.Infof("Failed to enable control on LED controller: %s", err)
	}

	if syncEnabled {
		// We wait for at least one sync to happen, or fail
		<-c.StartSyncing(config.MustDuration("homecloud.sync.interval"))
	}

	c.AutoStartModules()

	return nil
}

// if config.Bool(false, "clearcloud") {

func (c *HomeCloud) ClearCloud() {
	log.Infof("Clearing all cloud data in 5 seconds")

	time.Sleep(time.Second * 5)

	c.ThingModel.ClearCloud()
	c.ChannelModel.ClearCloud()
	c.DeviceModel.ClearCloud()
	c.RoomModel.ClearCloud()
	c.SiteModel.ClearCloud()

	log.Infof("All cloud data cleared? Probably.")
}

func (c *HomeCloud) ExportRPCServices() {
	c.Conn.MustExportService(c.ThingModel, "$home/services/ThingModel", &model.ServiceAnnouncement{
		Schema: "/service/thing-model",
	})
	c.Conn.MustExportService(c.DeviceModel, "$home/services/DeviceModel", &model.ServiceAnnouncement{
		Schema: "/service/device-model",
	})
	c.Conn.MustExportService(c.RoomModel, "$home/services/RoomModel", &model.ServiceAnnouncement{
		Schema: "/service/room-model",
	})
	c.Conn.MustExportService(c.SiteModel, "$home/services/SiteModel", &model.ServiceAnnouncement{
		Schema: "/service/site-model",
	})
}

type syncable interface {
	Sync(timeout time.Duration) error
}

func (c *HomeCloud) StartSyncing(interval time.Duration) chan bool {

	syncComplete := make(chan bool)

	syncModels := []syncable{c.RoomModel, c.DeviceModel, c.ChannelModel, c.ThingModel, c.ThingModel}

	go func() {
		for {

			c.log.Infof("\n\n\n------ Timed model syncing started (every %s) ------ ", interval.String())

			var wg sync.WaitGroup

			wg.Add(len(syncModels))

			success := true

			for _, model := range syncModels {
				go func(model syncable) {
					err := model.Sync(syncTimeout)
					if err != nil {
						c.log.Warningf("Failed to sync model %s : %s", reflect.TypeOf(model).String(), err)
						success = false
					}
					wg.Done()
				}(model)
			}

			wg.Wait()

			log.Infof("------ Timed model syncing complete. Success? %t ------\n\n\n", success)

			select {
			case syncComplete <- success:
			default:
			}

			time.Sleep(interval)
		}
	}()

	return syncComplete
}

func (c *HomeCloud) ensureSiteExists() {
	site, err := c.SiteModel.Fetch(config.MustString("siteId"))
	if err != nil && err != models.RecordNotFound {
		log.Fatalf("Failed to get site: %s", err)
	}

	if err == models.RecordNotFound {
		siteType := "home"
		name := "Home"
		site = &model.Site{
			Name: &name,
			ID:   config.MustString("siteId"),
			Type: &siteType,
		}
		err = c.SiteModel.Create(site)
		if err != nil && err != models.RecordNotFound {
			log.Fatalf("Failed to create site: %s", err)
		}
	}

}

func (c *HomeCloud) AutoStartModules() {

	do := func(name string, task string) error {
		return c.Conn.SendNotification("$node/"+config.Serial()+"/module/"+task, name)
	}

	interval := config.MustDuration("homecloud.autoStart.interval")

	for _, name := range c.findAutoStartModules() {
		log.Infof("-- (Re)starting '%s'", name)

		err := do(name, "stop")
		if err != nil {
			log.Fatalf("Failed to send %s stop message! %s", name, err)
		}

		time.Sleep(interval)

		err = do(name, "start")
		if err != nil {
			log.Fatalf("Failed to send %s start message! %s", name, err)
		}
	}

}

func (c *HomeCloud) findAutoStartModules() []string {
	modules := config.MustStringArray("homecloud.autoStart.modules")

	// for convenience, start all drivers and apps in the userdata partition
	// for ease of installation until official app provisioning is provided
	autoStartDirs, err := filepath.Glob("/data/sphere/user-autostart/*/*")
	if err != nil {
		return modules
	}

	for _, path := range autoStartDirs {
		moduleType := filepath.Base(filepath.Dir(path))
		if moduleType == "drivers" || moduleType == "apps" {
			modules = append(modules, filepath.Base(path))
		}
	}

	return modules
}
<|endoftext|>


<commit_before>package quickpgp

import (
	"crypto"
	"errors"
	"fmt"
	"os"

	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/armor"
	"golang.org/x/crypto/openpgp/packet"
)

func entityData() (name string, comment string, email string) {
	name = os.Getenv("LOGNAME")
	comment = os.Getenv("COMMENT")
	email = fmt.Sprintf("%s@%s", name, os.Getenv("HOSTNAME"))
	return
}

func isExistingDirectory(p string) bool {
	s, err := os.Stat(p)
	if err != nil {
		return false
	}
	return s.Mode().IsDir()
}

func GenerateKey(keyFileBase string) (err error) {
	if len(keyFileBase) == 0 {
		return errors.New("key file basename must not be emtpy")
	}
	if isExistingDirectory(keyFileBase) {
		return errors.New("key file basename must not be a directory")
	}

	var f *os.File
	var e *openpgp.Entity
	name, comment, email := entityData()
	e, err = openpgp.NewEntity(name, comment, email, nil)
	if err != nil {
		return err
	}
	for _, id := range e.Identities {
		id.SelfSignature.PreferredSymmetric = []uint8{
			// uint8(packet.CipherAES256),
			// uint8(packet.CipherAES192),
			// uint8(packet.CipherAES128),
			uint8(packet.CipherCAST5),
			// uint8(packet.Cipher3DES),
		}
		id.SelfSignature.PreferredHash = []uint8{
			// uint8(crypto.MD5SHA1),
			// uint8(crypto.MD5),
			uint8(crypto.RIPEMD160),
			// uint8(crypto.SHA3_224),
			// uint8(crypto.SHA3_256),
		}
		id.SelfSignature.PreferredCompression = []uint8{
			uint8(packet.CompressionNone),
		}
		err := id.SelfSignature.SignUserId(id.UserId.Id, e.PrimaryKey, e.PrivateKey, nil)
		if err != nil {
			return err
		}
	}

	f, err = os.Create(keyFileBase + ".key.asc")
	if err != nil {
		return err
	}
	defer f.Close()
	if err = f.Chmod(0600); err != nil {
		return err
	}
	w, err := armor.Encode(f, openpgp.PrivateKeyType, nil)
	if err != nil {
		return err
	}
	e.SerializePrivate(w, nil)
	w.Close()
	f.Write([]byte{'\n'})

	f, err = os.Create(keyFileBase + ".pub.asc")
	if err != nil {
		return err
	}
	defer f.Close()
	w, err = armor.Encode(f, openpgp.PublicKeyType, nil)
	if err != nil {
		return err
	}
	e.Serialize(w)
	w.Close()
	f.Write([]byte{'\n'})

	return nil
}
<commit_msg>use correct pgp hash identifiers in key generation<commit_after>package quickpgp

import (
	"crypto"
	"errors"
	"fmt"
	"os"

	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/armor"
	"golang.org/x/crypto/openpgp/packet"
	"golang.org/x/crypto/openpgp/s2k"
)

func entityData() (name string, comment string, email string) {
	name = os.Getenv("LOGNAME")
	comment = os.Getenv("COMMENT")
	email = fmt.Sprintf("%s@%s", name, os.Getenv("HOSTNAME"))
	return
}

func isExistingDirectory(p string) bool {
	s, err := os.Stat(p)
	if err != nil {
		return false
	}
	return s.Mode().IsDir()
}

func hashToHashId(h crypto.Hash) uint8 {
	v, ok := s2k.HashToHashId(h)
	if !ok {
		panic("tried to convert unknown hash")
	}
	return v
}

func GenerateKey(keyFileBase string) (err error) {
	if len(keyFileBase) == 0 {
		return errors.New("key file basename must not be emtpy")
	}
	if isExistingDirectory(keyFileBase) {
		return errors.New("key file basename must not be a directory")
	}

	var f *os.File
	var e *openpgp.Entity
	name, comment, email := entityData()
	e, err = openpgp.NewEntity(name, comment, email, nil)
	if err != nil {
		return err
	}
	for _, id := range e.Identities {
		id.SelfSignature.PreferredSymmetric = []uint8{
			uint8(packet.CipherAES128),
			uint8(packet.CipherAES256),
			uint8(packet.CipherAES192),
			uint8(packet.CipherCAST5),
		}
		id.SelfSignature.PreferredHash = []uint8{
			hashToHashId(crypto.RIPEMD160),
			hashToHashId(crypto.SHA256),
			hashToHashId(crypto.SHA384),
			hashToHashId(crypto.SHA512),
			hashToHashId(crypto.SHA224),
			hashToHashId(crypto.MD5),
		}
		id.SelfSignature.PreferredCompression = []uint8{
			uint8(packet.CompressionNone),
		}
		err := id.SelfSignature.SignUserId(id.UserId.Id, e.PrimaryKey, e.PrivateKey, nil)
		if err != nil {
			return err
		}
	}

	f, err = os.Create(keyFileBase + ".key.asc")
	if err != nil {
		return err
	}
	defer f.Close()
	if err = f.Chmod(0600); err != nil {
		return err
	}
	w, err := armor.Encode(f, openpgp.PrivateKeyType, nil)
	if err != nil {
		return err
	}
	e.SerializePrivate(w, nil)
	w.Close()
	f.Write([]byte{'\n'})

	f, err = os.Create(keyFileBase + ".pub.asc")
	if err != nil {
		return err
	}
	defer f.Close()
	w, err = armor.Encode(f, openpgp.PublicKeyType, nil)
	if err != nil {
		return err
	}
	e.Serialize(w)
	w.Close()
	f.Write([]byte{'\n'})

	return nil
}
<|endoftext|>