/* Copyright 2014 The go-marathon Authors 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 ( "fmt" "time" ) // Group is a marathon application group type Group struct { ID string `json:"id"` Apps []*Application `json:"apps"` Dependencies []string `json:"dependencies"` Groups []*Group `json:"groups"` } // Groups is a collection of marathon application groups type Groups struct { ID string `json:"id"` Apps []*Application `json:"apps"` Dependencies []string `json:"dependencies"` Groups []*Group `json:"groups"` } // GetGroupOpts contains a payload for Group and Groups method // embed: Embeds nested resources that match the supplied path. // You can specify this parameter multiple times with different values type GetGroupOpts struct { Embed []string `url:"embed,omitempty"` } // DeleteGroupOpts contains a payload for DeleteGroup method // force: overrides a currently running deployment. type DeleteGroupOpts struct { Force bool `url:"force,omitempty"` } // UpdateGroupOpts contains a payload for UpdateGroup method // force: overrides a currently running deployment. type UpdateGroupOpts struct { Force bool `url:"force,omitempty"` } // NewApplicationGroup create a new application group // name: the name of the group func NewApplicationGroup(name string) *Group { return &Group{ ID: name, Apps: make([]*Application, 0), Dependencies: make([]string, 0), Groups: make([]*Group, 0), } } // Name sets the name of the group // name: the name of the group func (r *Group) Name(name string) *Group { r.ID = validateID(name) return r } // App add a application to the group in question // application: a pointer to the Application func (r *Group) App(application *Application) *Group { if r.Apps == nil { r.Apps = make([]*Application, 0) } r.Apps = append(r.Apps, application) return r } // Groups retrieves a list of all the groups from marathon func (r *marathonClient) Groups() (*Groups, error) { groups := new(Groups) if err := r.apiGet(marathonAPIGroups, "", groups); err != nil { return nil, err } return groups, nil } // Group retrieves the configuration of a specific group from marathon // name: the identifier for the group func (r *marathonClient) Group(name string) (*Group, error) { group := new(Group) if err := r.apiGet(fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)), nil, group); err != nil { return nil, err } return group, nil } // GroupsBy retrieves a list of all the groups from marathon by embed options // opts: GetGroupOpts request payload func (r *marathonClient) GroupsBy(opts *GetGroupOpts) (*Groups, error) { path, err := addOptions(marathonAPIGroups, opts) if err != nil { return nil, err } groups := new(Groups) if err := r.apiGet(path, "", groups); err != nil { return nil, err } return groups, nil } // GroupBy retrieves the configuration of a specific group from marathon // name: the identifier for the group // opts: GetGroupOpts request payload func (r *marathonClient) GroupBy(name string, opts *GetGroupOpts) (*Group, error) { path, err := addOptions(fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)), opts) if err != nil { return nil, err } group := new(Group) if err := r.apiGet(path, nil, group); err != nil { return nil, err } return group, nil } // HasGroup checks if the group exists in marathon // name: the identifier for the group func (r *marathonClient) HasGroup(name string) (bool, error) { path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)) err := r.apiGet(path, "", nil) if err != nil { if apiErr, ok := err.(*APIError); ok && apiErr.ErrCode == ErrCodeNotFound { return false, nil } return false, err } return true, nil } // CreateGroup creates a new group in marathon // group: a pointer the Group structure defining the group func (r *marathonClient) CreateGroup(group *Group) error { return r.apiPost(marathonAPIGroups, group, nil) } // WaitOnGroup waits for all the applications in a group to be deployed // group: the identifier for the group // timeout: a duration of time to wait before considering it failed (all tasks in all apps running defined as deployed) func (r *marathonClient) WaitOnGroup(name string, timeout time.Duration) error { err := deadline(timeout, func(stop_channel chan bool) error { var flick atomicSwitch go func() { <-stop_channel close(stop_channel) flick.SwitchOn() }() for !flick.IsSwitched() { if group, err := r.Group(name); err != nil { continue } else { allRunning := true // for each of the application, check if the tasks and running for _, appID := range group.Apps { // Arrrgghhh!! .. so we can't use application instances from the Application struct like with app wait on as it // appears the instance count is not set straight away!! .. it defaults to zero and changes probably at the // dependencies gets deployed. Which is probably how it internally handles dependencies .. // step: grab the application application, err := r.Application(appID.ID) if err != nil { allRunning = false break } if application.Tasks == nil { allRunning = false } else if len(application.Tasks) != *appID.Instances { allRunning = false } else if application.TasksRunning != *appID.Instances { allRunning = false } else if len(application.DeploymentIDs()) > 0 { allRunning = false } } // has anyone toggle the flag? if allRunning { return nil } } time.Sleep(r.config.PollingWaitTime) } return nil }) return err } // DeleteGroup deletes a group from marathon // name: the identifier for the group // force: used to force the delete operation in case of blocked deployment func (r *marathonClient) DeleteGroup(name string, force bool) (*DeploymentID, error) { version := new(DeploymentID) path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)) if force { path += "?force=true" } if err := r.apiDelete(path, nil, version); err != nil { return nil, err } return version, nil } // UpdateGroup updates the parameters of a groups // name: the identifier for the group // group: the group structure with the new params // force: used to force the update operation in case of blocked deployment func (r *marathonClient) UpdateGroup(name string, group *Group, force bool) (*DeploymentID, error) { deploymentID := new(DeploymentID) path := fmt.Sprintf("%s/%s", marathonAPIGroups, trimRootPath(name)) if force { path += "?force=true" } if err := r.apiPut(path, group, deploymentID); err != nil { return nil, err } return deploymentID, nil }