1/* 2Copyright 2014 The go-marathon Authors All rights reserved. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package marathon 18 19import ( 20 "fmt" 21 "strings" 22) 23 24// Tasks is a collection of marathon tasks 25type Tasks struct { 26 Tasks []Task `json:"tasks"` 27} 28 29// Task is the definition for a marathon task 30type Task struct { 31 ID string `json:"id"` 32 AppID string `json:"appId"` 33 Host string `json:"host"` 34 HealthCheckResults []*HealthCheckResult `json:"healthCheckResults"` 35 Ports []int `json:"ports"` 36 ServicePorts []int `json:"servicePorts"` 37 SlaveID string `json:"slaveId"` 38 StagedAt string `json:"stagedAt"` 39 StartedAt string `json:"startedAt"` 40 State string `json:"state"` 41 IPAddresses []*IPAddress `json:"ipAddresses"` 42 Version string `json:"version"` 43} 44 45// IPAddress represents a task's IP address and protocol. 46type IPAddress struct { 47 IPAddress string `json:"ipAddress"` 48 Protocol string `json:"protocol"` 49} 50 51// AllTasksOpts contains a payload for AllTasks method 52// status: Return only those tasks whose status matches this parameter. 53// If not specified, all tasks are returned. Possible values: running, staging. Default: none. 54type AllTasksOpts struct { 55 Status string `url:"status,omitempty"` 56} 57 58// KillApplicationTasksOpts contains a payload for KillApplicationTasks method 59// host: kill only those tasks on a specific host (optional) 60// scale: Scale the app down (i.e. decrement its instances setting by the number of tasks killed) after killing the specified tasks 61type KillApplicationTasksOpts struct { 62 Host string `url:"host,omitempty"` 63 Scale bool `url:"scale,omitempty"` 64 Force bool `url:"force,omitempty"` 65} 66 67// KillTaskOpts contains a payload for task killing methods 68// scale: Scale the app down 69type KillTaskOpts struct { 70 Scale bool `url:"scale,omitempty"` 71 Force bool `url:"force,omitempty"` 72} 73 74// HasHealthCheckResults checks if the task has any health checks 75func (r *Task) HasHealthCheckResults() bool { 76 return r.HealthCheckResults != nil && len(r.HealthCheckResults) > 0 77} 78 79// AllTasks lists tasks of all applications. 80// opts: AllTasksOpts request payload 81func (r *marathonClient) AllTasks(opts *AllTasksOpts) (*Tasks, error) { 82 path, err := addOptions(marathonAPITasks, opts) 83 if err != nil { 84 return nil, err 85 } 86 87 tasks := new(Tasks) 88 if err := r.apiGet(path, nil, tasks); err != nil { 89 return nil, err 90 } 91 92 return tasks, nil 93} 94 95// Tasks retrieves a list of tasks for an application 96// id: the id of the application 97func (r *marathonClient) Tasks(id string) (*Tasks, error) { 98 tasks := new(Tasks) 99 if err := r.apiGet(fmt.Sprintf("%s/%s/tasks", marathonAPIApps, trimRootPath(id)), nil, tasks); err != nil { 100 return nil, err 101 } 102 103 return tasks, nil 104} 105 106// KillApplicationTasks kills all tasks relating to an application 107// id: the id of the application 108// opts: KillApplicationTasksOpts request payload 109func (r *marathonClient) KillApplicationTasks(id string, opts *KillApplicationTasksOpts) (*Tasks, error) { 110 path := fmt.Sprintf("%s/%s/tasks", marathonAPIApps, trimRootPath(id)) 111 path, err := addOptions(path, opts) 112 if err != nil { 113 return nil, err 114 } 115 116 tasks := new(Tasks) 117 if err := r.apiDelete(path, nil, tasks); err != nil { 118 return nil, err 119 } 120 121 return tasks, nil 122} 123 124// KillTask kills the task associated with a given ID 125// taskID: the id for the task 126// opts: KillTaskOpts request payload 127func (r *marathonClient) KillTask(taskID string, opts *KillTaskOpts) (*Task, error) { 128 appName := taskID[0:strings.LastIndex(taskID, ".")] 129 appName = strings.Replace(appName, "_", "/", -1) 130 taskID = strings.Replace(taskID, "/", "_", -1) 131 132 path := fmt.Sprintf("%s/%s/tasks/%s", marathonAPIApps, appName, taskID) 133 path, err := addOptions(path, opts) 134 if err != nil { 135 return nil, err 136 } 137 138 wrappedTask := new(struct { 139 Task Task `json:"task"` 140 }) 141 142 if err := r.apiDelete(path, nil, wrappedTask); err != nil { 143 return nil, err 144 } 145 146 return &wrappedTask.Task, nil 147} 148 149// KillTasks kills tasks associated with given array of ids 150// tasks: the array of task ids 151// opts: KillTaskOpts request payload 152func (r *marathonClient) KillTasks(tasks []string, opts *KillTaskOpts) error { 153 path := fmt.Sprintf("%s/delete", marathonAPITasks) 154 path, err := addOptions(path, opts) 155 if err != nil { 156 return nil 157 } 158 159 var post struct { 160 IDs []string `json:"ids"` 161 } 162 post.IDs = tasks 163 164 return r.apiPost(path, &post, nil) 165} 166 167// TaskEndpoints gets the endpoints i.e. HOST_IP:DYNAMIC_PORT for a specific application service 168// I.e. a container running apache, might have ports 80/443 (translated to X dynamic ports), but i want 169// port 80 only and i only want those whom have passed the health check 170// 171// Note: I've NO IDEA how to associate the health_check_result to the actual port, I don't think it's 172// possible at the moment, however, given marathon will fail and restart an application even if one of x ports of a task is 173// down, the per port check is redundant??? .. personally, I like it anyhow, but hey 174// 175 176// name: the identifier for the application 177// port: the container port you are interested in 178// health: whether to check the health or not 179func (r *marathonClient) TaskEndpoints(name string, port int, healthCheck bool) ([]string, error) { 180 // step: get the application details 181 application, err := r.Application(name) 182 if err != nil { 183 return nil, err 184 } 185 186 // step: we need to get the port index of the service we are interested in 187 portIndex, err := application.Container.Docker.ServicePortIndex(port) 188 if err != nil { 189 portIndex, err = application.Container.ServicePortIndex(port) 190 if err != nil { 191 return nil, err 192 } 193 } 194 195 // step: do we have any tasks? 196 if application.Tasks == nil || len(application.Tasks) == 0 { 197 return nil, nil 198 } 199 200 // step: if we are checking health the 'service' has a health check? 201 healthCheck = healthCheck && application.HasHealthChecks() 202 203 // step: iterate the tasks and extract the dynamic ports 204 var list []string 205 for _, task := range application.Tasks { 206 if !healthCheck || task.allHealthChecksAlive() { 207 endpoint := fmt.Sprintf("%s:%d", task.Host, task.Ports[portIndex]) 208 list = append(list, endpoint) 209 } 210 } 211 212 return list, nil 213} 214 215func (r *Task) allHealthChecksAlive() bool { 216 // check: does the task have a health check result, if NOT, it's because the 217 // health of the task hasn't yet been performed, hence we assume it as DOWN 218 if !r.HasHealthCheckResults() { 219 return false 220 } 221 // step: check the health results then 222 for _, check := range r.HealthCheckResults { 223 if !check.Alive { 224 return false 225 } 226 } 227 228 return true 229} 230