traefik/integration/vendor/github.com/gambol99/go-marathon/application.go
2017-03-09 13:13:03 +01:00

643 lines
20 KiB
Go

/*
Copyright 2014 Rohith All rights reserved.
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.
*/
package marathon
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"time"
)
var (
// ErrNoApplicationContainer is thrown when a container has been specified yet
ErrNoApplicationContainer = errors.New("you have not specified a docker container yet")
)
// Applications is a collection of applications
type Applications struct {
Apps []Application `json:"apps"`
}
// Application is the definition for an application in marathon
type Application struct {
ID string `json:"id,omitempty"`
Cmd *string `json:"cmd,omitempty"`
Args *[]string `json:"args,omitempty"`
Constraints *[][]string `json:"constraints,omitempty"`
Container *Container `json:"container,omitempty"`
CPUs float64 `json:"cpus,omitempty"`
Disk *float64 `json:"disk,omitempty"`
Env *map[string]string `json:"env,omitempty"`
Executor *string `json:"executor,omitempty"`
HealthChecks *[]HealthCheck `json:"healthChecks,omitempty"`
Instances *int `json:"instances,omitempty"`
Mem *float64 `json:"mem,omitempty"`
Tasks []*Task `json:"tasks,omitempty"`
Ports []int `json:"ports"`
RequirePorts *bool `json:"requirePorts,omitempty"`
BackoffSeconds *float64 `json:"backoffSeconds,omitempty"`
BackoffFactor *float64 `json:"backoffFactor,omitempty"`
MaxLaunchDelaySeconds *float64 `json:"maxLaunchDelaySeconds,omitempty"`
Deployments []map[string]string `json:"deployments,omitempty"`
Dependencies []string `json:"dependencies"`
TasksRunning int `json:"tasksRunning,omitempty"`
TasksStaged int `json:"tasksStaged,omitempty"`
TasksHealthy int `json:"tasksHealthy,omitempty"`
TasksUnhealthy int `json:"tasksUnhealthy,omitempty"`
User string `json:"user,omitempty"`
UpgradeStrategy *UpgradeStrategy `json:"upgradeStrategy,omitempty"`
Uris *[]string `json:"uris"`
Version string `json:"version,omitempty"`
VersionInfo *VersionInfo `json:"versionInfo,omitempty"`
Labels *map[string]string `json:"labels,omitempty"`
AcceptedResourceRoles []string `json:"acceptedResourceRoles,omitempty"`
LastTaskFailure *LastTaskFailure `json:"lastTaskFailure,omitempty"`
Fetch []Fetch `json:"fetch"`
}
// ApplicationVersions is a collection of application versions for a specific app in marathon
type ApplicationVersions struct {
Versions []string `json:"versions"`
}
// ApplicationVersion is the application version response from marathon
type ApplicationVersion struct {
Version string `json:"version"`
}
// VersionInfo is the application versioning details from marathon
type VersionInfo struct {
LastScalingAt string `json:"lastScalingAt,omitempty"`
LastConfigChangeAt string `json:"lastConfigChangeAt,omitempty"`
}
// Fetch will download URI before task starts
type Fetch struct {
URI string `json:"uri"`
Executable bool `json:"executable"`
Extract bool `json:"extract"`
Cache bool `json:"cache"`
}
// NewDockerApplication creates a default docker application
func NewDockerApplication() *Application {
application := new(Application)
application.Container = NewDockerContainer()
return application
}
// Name sets the name / ID of the application i.e. the identifier for this application
func (r *Application) Name(id string) *Application {
r.ID = validateID(id)
return r
}
// Command sets the cmd of the application
func (r *Application) Command(cmd string) *Application {
r.Cmd = &cmd
return r
}
// CPU set the amount of CPU shares per instance which is assigned to the application
// cpu: the CPU shared (check Docker docs) per instance
func (r *Application) CPU(cpu float64) *Application {
r.CPUs = cpu
return r
}
// Storage sets the amount of disk space the application is assigned, which for docker
// application I don't believe is relevant
// disk: the disk space in MB
func (r *Application) Storage(disk float64) *Application {
r.Disk = &disk
return r
}
// AllTaskRunning checks to see if all the application tasks are running, i.e. the instances is equal
// to the number of running tasks
func (r *Application) AllTaskRunning() bool {
if r.Instances == nil || *r.Instances == 0 {
return true
}
if r.Tasks == nil {
return false
}
if r.TasksRunning == *r.Instances {
return true
}
return false
}
// DependsOn adds one or more dependencies for this application. Note, if you want to wait for
// an application dependency to actually be UP, i.e. not just deployed, you need a health check
// on the dependant app.
// names: the application id(s) this application depends on
func (r *Application) DependsOn(names ...string) *Application {
if r.Dependencies == nil {
r.Dependencies = make([]string, 0)
}
r.Dependencies = append(r.Dependencies, names...)
return r
}
// Memory sets he amount of memory the application can consume per instance
// memory: the amount of MB to assign
func (r *Application) Memory(memory float64) *Application {
r.Mem = &memory
return r
}
// Count sets the number of instances of the application to run
// count: the number of instances to run
func (r *Application) Count(count int) *Application {
r.Instances = &count
return r
}
// AddArgs adds one or more arguments to the applications
// arguments: the argument(s) you are adding
func (r *Application) AddArgs(arguments ...string) *Application {
if r.Args == nil {
r.EmptyArgs()
}
args := *r.Args
args = append(args, arguments...)
r.Args = &args
return r
}
// EmptyArgs explicitly empties arguments -- use this if you need to empty
// arguments of an application that already has arguments set (setting args to nil will
// keep the current value)
func (r *Application) EmptyArgs() *Application {
r.Args = &[]string{}
return r
}
// AddConstraint adds a new constraint
// constraints: the constraint definition, one constraint per array element
func (r *Application) AddConstraint(constraints ...string) *Application {
if r.Constraints == nil {
r.EmptyConstraints()
}
c := *r.Constraints
c = append(c, constraints)
r.Constraints = &c
return r
}
// EmptyConstraints explicitly empties constraints -- use this if you need to empty
// constraints of an application that already has constraints set (setting constraints to nil will
// keep the current value)
func (r *Application) EmptyConstraints() *Application {
r.Constraints = &[][]string{}
return r
}
// AddLabel adds a label to the application
// name: the name of the label
// value: value for this label
func (r *Application) AddLabel(name, value string) *Application {
if r.Labels == nil {
r.EmptyLabels()
}
(*r.Labels)[name] = value
return r
}
// EmptyLabels explicitly empties the labels -- use this if you need to empty
// the labels of an application that already has labels set (setting labels to nil will
// keep the current value)
func (r *Application) EmptyLabels() *Application {
r.Labels = &map[string]string{}
return r
}
// AddEnv adds an environment variable to the application
// name: the name of the variable
// value: go figure, the value associated to the above
func (r *Application) AddEnv(name, value string) *Application {
if r.Env == nil {
r.EmptyEnvs()
}
(*r.Env)[name] = value
return r
}
// EmptyEnvs explicitly empties the envs -- use this if you need to empty
// the environments of an application that already has environments set (setting env to nil will
// keep the current value)
func (r *Application) EmptyEnvs() *Application {
r.Env = &map[string]string{}
return r
}
// SetExecutor sets the executor
func (r *Application) SetExecutor(executor string) *Application {
r.Executor = &executor
return r
}
// AddHealthCheck adds a health check
// healthCheck the health check that should be added
func (r *Application) AddHealthCheck(healthCheck HealthCheck) *Application {
if r.HealthChecks == nil {
r.EmptyHealthChecks()
}
healthChecks := *r.HealthChecks
healthChecks = append(healthChecks, healthCheck)
r.HealthChecks = &healthChecks
return r
}
// EmptyHealthChecks explicitly empties health checks -- use this if you need to empty
// health checks of an application that already has health checks set (setting health checks to nil will
// keep the current value)
func (r *Application) EmptyHealthChecks() *Application {
r.HealthChecks = &[]HealthCheck{}
return r
}
// HasHealthChecks is a helper method, used to check if an application has health checks
func (r *Application) HasHealthChecks() bool {
return r.HealthChecks != nil && len(*r.HealthChecks) > 0
}
// DeploymentIDs retrieves the application deployments IDs
func (r *Application) DeploymentIDs() []*DeploymentID {
var deployments []*DeploymentID
if r.Deployments == nil {
return deployments
}
// step: extract the deployment id from the result
for _, deploy := range r.Deployments {
if id, found := deploy["id"]; found {
deployment := &DeploymentID{
Version: r.Version,
DeploymentID: id,
}
deployments = append(deployments, deployment)
}
}
return deployments
}
// CheckHTTP adds a HTTP check to an application
// port: the port the check should be checking
// interval: the interval in seconds the check should be performed
func (r *Application) CheckHTTP(uri string, port, interval int) (*Application, error) {
if r.Container == nil || r.Container.Docker == nil {
return nil, ErrNoApplicationContainer
}
// step: get the port index
portIndex, err := r.Container.Docker.ServicePortIndex(port)
if err != nil {
return nil, err
}
health := NewDefaultHealthCheck()
health.IntervalSeconds = interval
*health.Path = uri
*health.PortIndex = portIndex
// step: add to the checks
r.AddHealthCheck(*health)
return r, nil
}
// CheckTCP adds a TCP check to an application; note the port mapping must already exist, or an
// error will thrown
// port: the port the check should, err, check
// interval: the interval in seconds the check should be performed
func (r *Application) CheckTCP(port, interval int) (*Application, error) {
if r.Container == nil || r.Container.Docker == nil {
return nil, ErrNoApplicationContainer
}
// step: get the port index
portIndex, err := r.Container.Docker.ServicePortIndex(port)
if err != nil {
return nil, err
}
health := NewDefaultHealthCheck()
health.Protocol = "TCP"
health.IntervalSeconds = interval
*health.PortIndex = portIndex
// step: add to the checks
r.AddHealthCheck(*health)
return r, nil
}
// AddUris adds one or more uris to the applications
// arguments: the uri(s) you are adding
func (r *Application) AddUris(newUris ...string) *Application {
if r.Uris == nil {
r.EmptyUris()
}
uris := *r.Uris
uris = append(uris, newUris...)
r.Uris = &uris
return r
}
// EmptyUris explicitly empties uris -- use this if you need to empty
// uris of an application that already has uris set (setting uris to nil will
// keep the current value)
func (r *Application) EmptyUris() *Application {
r.Uris = &[]string{}
return r
}
// String returns the json representation of this application
func (r *Application) String() string {
s, err := json.MarshalIndent(r, "", " ")
if err != nil {
return fmt.Sprintf(`{"error": "error decoding type into json: %s"}`, err)
}
return string(s)
}
// Applications retrieves an array of all the applications which are running in marathon
func (r *marathonClient) Applications(v url.Values) (*Applications, error) {
applications := new(Applications)
err := r.apiGet(marathonAPIApps+"?"+v.Encode(), nil, applications)
if err != nil {
return nil, err
}
return applications, nil
}
// ListApplications retrieves an array of the application names currently running in marathon
func (r *marathonClient) ListApplications(v url.Values) ([]string, error) {
applications, err := r.Applications(v)
if err != nil {
return nil, err
}
var list []string
for _, application := range applications.Apps {
list = append(list, application.ID)
}
return list, nil
}
// HasApplicationVersion checks to see if the application version exists in Marathon
// name: the id used to identify the application
// version: the version (normally a timestamp) your looking for
func (r *marathonClient) HasApplicationVersion(name, version string) (bool, error) {
id := trimRootPath(name)
versions, err := r.ApplicationVersions(id)
if err != nil {
return false, err
}
return contains(versions.Versions, version), nil
}
// ApplicationVersions is a list of versions which has been deployed with marathon for a specific application
// name: the id used to identify the application
func (r *marathonClient) ApplicationVersions(name string) (*ApplicationVersions, error) {
uri := fmt.Sprintf("%s/versions", buildURI(name))
versions := new(ApplicationVersions)
if err := r.apiGet(uri, nil, versions); err != nil {
return nil, err
}
return versions, nil
}
// SetApplicationVersion changes the version of the application
// name: the id used to identify the application
// version: the version (normally a timestamp) you wish to change to
func (r *marathonClient) SetApplicationVersion(name string, version *ApplicationVersion) (*DeploymentID, error) {
uri := fmt.Sprintf(buildURI(name))
deploymentID := new(DeploymentID)
if err := r.apiPut(uri, version, deploymentID); err != nil {
return nil, err
}
return deploymentID, nil
}
// Application retrieves the application configuration from marathon
// name: the id used to identify the application
func (r *marathonClient) Application(name string) (*Application, error) {
var wrapper struct {
Application *Application `json:"app"`
}
if err := r.apiGet(buildURI(name), nil, &wrapper); err != nil {
return nil, err
}
return wrapper.Application, nil
}
// ApplicationByVersion retrieves the application configuration from marathon
// name: the id used to identify the application
// version: the version of the configuration you would like to receive
func (r *marathonClient) ApplicationByVersion(name, version string) (*Application, error) {
var app *Application
uri := fmt.Sprintf("%s/versions/%s", buildURI(name), version)
if err := r.apiGet(uri, nil, &app); err != nil {
return nil, err
}
return app, nil
}
// ApplicationOK validates that the application, or more appropriately it's tasks have passed all the health checks.
// If no health checks exist, we simply return true
// name: the id used to identify the application
func (r *marathonClient) ApplicationOK(name string) (bool, error) {
// step: get the application
application, err := r.Application(name)
if err != nil {
return false, err
}
// step: check if all the tasks are running?
if !application.AllTaskRunning() {
return false, nil
}
// step: if the application has not health checks, just return true
if application.HealthChecks == nil || len(*application.HealthChecks) == 0 {
return true, nil
}
// step: iterate the application checks and look for false
for _, task := range application.Tasks {
for _, check := range task.HealthCheckResults {
//When a task is flapping in Marathon, this is sometimes nil
if check == nil || !check.Alive {
return false, nil
}
}
}
return true, nil
}
// ApplicationDeployments retrieves an array of Deployment IDs for an application
// name: the id used to identify the application
func (r *marathonClient) ApplicationDeployments(name string) ([]*DeploymentID, error) {
application, err := r.Application(name)
if err != nil {
return nil, err
}
return application.DeploymentIDs(), nil
}
// CreateApplication creates a new application in Marathon
// application: the structure holding the application configuration
func (r *marathonClient) CreateApplication(application *Application) (*Application, error) {
result := new(Application)
if err := r.apiPost(marathonAPIApps, &application, result); err != nil {
return nil, err
}
return result, nil
}
// WaitOnApplication waits for an application to be deployed
// name: the id of the application
// timeout: a duration of time to wait for an application to deploy
func (r *marathonClient) WaitOnApplication(name string, timeout time.Duration) error {
if r.appExistAndRunning(name) {
return nil
}
ticker := time.NewTicker(time.Millisecond * 500)
defer ticker.Stop()
for {
select {
case <-time.After(timeout):
return ErrTimeoutError
case <-ticker.C:
if r.appExistAndRunning(name) {
return nil
}
}
}
}
func (r *marathonClient) appExistAndRunning(name string) bool {
app, err := r.Application(name)
if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound {
return false
}
if err == nil && app.AllTaskRunning() {
return true
}
return false
}
// DeleteApplication deletes an application from marathon
// name: the id used to identify the application
func (r *marathonClient) DeleteApplication(name string) (*DeploymentID, error) {
// step: check of the application already exists
deployID := new(DeploymentID)
if err := r.apiDelete(buildURI(name), nil, deployID); err != nil {
return nil, err
}
return deployID, nil
}
// RestartApplication performs a rolling restart of marathon application
// name: the id used to identify the application
func (r *marathonClient) RestartApplication(name string, force bool) (*DeploymentID, error) {
deployment := new(DeploymentID)
var options struct {
Force bool `json:"force"`
}
options.Force = force
if err := r.apiPost(fmt.Sprintf("%s/restart", buildURI(name)), &options, deployment); err != nil {
return nil, err
}
return deployment, nil
}
// ScaleApplicationInstances changes the number of instance an application is running
// name: the id used to identify the application
// instances: the number of instances you wish to change to
// force: used to force the scale operation in case of blocked deployment
func (r *marathonClient) ScaleApplicationInstances(name string, instances int, force bool) (*DeploymentID, error) {
changes := new(Application)
changes.ID = validateID(name)
changes.Instances = &instances
uri := buildURIWithForceParam(name, force)
deployID := new(DeploymentID)
if err := r.apiPut(uri, &changes, deployID); err != nil {
return nil, err
}
return deployID, nil
}
// UpdateApplication updates an application in Marathon
// application: the structure holding the application configuration
func (r *marathonClient) UpdateApplication(application *Application, force bool) (*DeploymentID, error) {
result := new(DeploymentID)
uri := buildURIWithForceParam(application.ID, force)
if err := r.apiPut(uri, &application, result); err != nil {
return nil, err
}
return result, nil
}
func buildURIWithForceParam(path string, force bool) string {
uri := buildURI(path)
if force {
uri += "?force=true"
}
return uri
}
func buildURI(path string) string {
return fmt.Sprintf("%s/%s", marathonAPIApps, trimRootPath(path))
}