Skip to content

Commit e5da26c

Browse files
author
Gabriel Monroy
committed
Merge pull request #1 from deis/updatectl
Integrating updatectl logic to deisctl
2 parents 5602a17 + cb18218 commit e5da26c

4 files changed

Lines changed: 847 additions & 0 deletions

File tree

deisctl/deisctl.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strconv"
88

99
"github.com/deis/deisctl"
10+
"github.com/deis/deisctl/updatectl"
1011
docopt "github.com/docopt/docopt-go"
1112
)
1213

@@ -194,6 +195,8 @@ Options:
194195
err = cmdInstall(c)
195196
case "uninstall":
196197
err = cmdUninstall(c)
198+
case "update":
199+
updatectl.Update(os.Args)
197200
default:
198201
fmt.Printf(usage)
199202
os.Exit(2)

updatectl/instance.go

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
package updatectl
2+
3+
import (
4+
"bytes"
5+
"code.google.com/p/go-uuid/uuid"
6+
"encoding/xml"
7+
"fmt"
8+
"github.com/coreos/go-omaha/omaha"
9+
update "github.com/coreos/updatectl/client/update/v1"
10+
"github.com/deis/deisctl/utils"
11+
"io"
12+
"log"
13+
"math/rand"
14+
"net/http"
15+
"os"
16+
"path/filepath"
17+
"text/tabwriter"
18+
"time"
19+
)
20+
21+
const (
22+
initialInterval = time.Second * 10
23+
maxInterval = time.Minute * 7
24+
downloadDir = "/home/core/deis/systemd/"
25+
)
26+
27+
var (
28+
instanceFlags struct {
29+
groupId StringFlag
30+
appId StringFlag
31+
start int64
32+
end int64
33+
verbose bool
34+
clientsPerApp int
35+
minSleep int
36+
maxSleep int
37+
errorRate int
38+
OEM string
39+
pingOnly int
40+
version string
41+
}
42+
43+
cmdInstance = &Command{
44+
Name: "instance",
45+
Usage: "[OPTION]...",
46+
Summary: "Operations to view instances.",
47+
Subcommands: []*Command{
48+
cmdInstanceListUpdates,
49+
cmdInstanceListAppVersions,
50+
cmdInstanceFake,
51+
},
52+
}
53+
54+
cmdInstanceListUpdates = &Command{
55+
Name: "instance list-updates",
56+
Usage: "[OPTION]...",
57+
Description: "Generates a list of instance updates.",
58+
Run: instanceListUpdates,
59+
}
60+
61+
cmdInstanceListAppVersions = &Command{
62+
Name: "instance list-app-versions",
63+
Usage: "[OPTION]...",
64+
Description: "Generates a list of apps/versions with instance count.",
65+
Run: instanceListAppVersions,
66+
}
67+
68+
cmdInstanceDeis = &Command{
69+
Name: "instance deis",
70+
Usage: "[OPTION]...",
71+
Description: "Simulate single deis to update instances.",
72+
Run: instanceDeis,
73+
}
74+
)
75+
76+
func init() {
77+
cmdInstanceListUpdates.Flags.Var(&instanceFlags.groupId, "group-id", "Group id")
78+
cmdInstanceListUpdates.Flags.Var(&instanceFlags.appId, "app-id", "App id")
79+
cmdInstanceListUpdates.Flags.Int64Var(&instanceFlags.start, "start", 0, "Start date filter")
80+
cmdInstanceListUpdates.Flags.Int64Var(&instanceFlags.end, "end", 0, "End date filter")
81+
82+
cmdInstanceListAppVersions.Flags.Var(&instanceFlags.groupId, "group-id", "Group id")
83+
cmdInstanceListAppVersions.Flags.Var(&instanceFlags.appId, "app-id", "App id")
84+
cmdInstanceListAppVersions.Flags.Int64Var(&instanceFlags.start, "start", 0, "Start date filter")
85+
cmdInstanceListAppVersions.Flags.Int64Var(&instanceFlags.end, "end", 0, "End date filter")
86+
87+
cmdInstanceFake.Flags.BoolVar(&instanceFlags.verbose, "verbose", false, "Print out the request bodies")
88+
cmdInstanceFake.Flags.IntVar(&instanceFlags.clientsPerApp, "clients-per-app", 1, "Number of fake fents per appid.")
89+
cmdInstanceFake.Flags.IntVar(&instanceFlags.minSleep, "min-sleep", 5, "Minimum time between update checks.")
90+
cmdInstanceFake.Flags.IntVar(&instanceFlags.maxSleep, "max-sleep", 10, "Maximum time between update checks.")
91+
cmdInstanceFake.Flags.IntVar(&instanceFlags.errorRate, "errorrate", 1, "Chance of error (0-100)%.")
92+
cmdInstanceFake.Flags.StringVar(&instanceFlags.OEM, "oem", "fakeclient", "oem to report")
93+
// simulate reboot lock.
94+
cmdInstanceFake.Flags.IntVar(&instanceFlags.pingOnly, "ping-only", 0, "halt update and just send ping requests this many times.")
95+
cmdInstanceFake.Flags.Var(&instanceFlags.appId, os.Getenv("DEISCTL_APP_ID"), "Application ID to update.")
96+
instanceFlags.appId.required = true
97+
cmdInstanceFake.Flags.Var(&instanceFlags.groupId, os.Getenv("DEISCTL_GROUP_ID"), "Group ID to update.")
98+
instanceFlags.groupId.required = true
99+
cmdInstanceFake.Flags.StringVar(&instanceFlags.version, "version", os.Getenv("DEISCTL_APP_VERSION"), "Version to report.")
100+
}
101+
102+
func instanceListUpdates(args []string, service *update.Service, out *tabwriter.Writer) int {
103+
call := service.Clientupdate.List()
104+
call.DateStart(instanceFlags.start)
105+
call.DateEnd(instanceFlags.end)
106+
if instanceFlags.groupId.Get() != nil {
107+
call.GroupId(instanceFlags.groupId.String())
108+
}
109+
if instanceFlags.groupId.Get() != nil {
110+
call.AppId(instanceFlags.appId.String())
111+
}
112+
list, err := call.Do()
113+
114+
if err != nil {
115+
log.Fatal(err)
116+
}
117+
118+
fmt.Fprintln(out, "AppID\tClientID\tVersion\tLastSeen\tGroup\tStatus\tOEM")
119+
for _, cl := range list.Items {
120+
fmt.Fprintf(out, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", cl.AppId,
121+
cl.ClientId, cl.Version, cl.LastSeen, cl.GroupId,
122+
cl.Status, cl.Oem)
123+
}
124+
out.Flush()
125+
return OK
126+
}
127+
128+
func instanceListAppVersions(args []string, service *update.Service, out *tabwriter.Writer) int {
129+
call := service.Appversion.List()
130+
131+
if instanceFlags.groupId.Get() != nil {
132+
call.GroupId(instanceFlags.groupId.String())
133+
}
134+
if instanceFlags.appId.Get() != nil {
135+
call.AppId(instanceFlags.appId.String())
136+
}
137+
if instanceFlags.start != 0 {
138+
call.DateStart(instanceFlags.start)
139+
}
140+
141+
if instanceFlags.end != 0 {
142+
call.DateEnd(instanceFlags.end)
143+
}
144+
145+
list, err := call.Do()
146+
147+
if err != nil {
148+
log.Fatal(err)
149+
}
150+
151+
fmt.Fprintln(out, "AppID\tGroupID\tVersion\tClients")
152+
for _, cl := range list.Items {
153+
fmt.Fprintf(out, "%s\t%s\t%s\t%d\n", cl.AppId, cl.GroupId, cl.Version, cl.Count)
154+
}
155+
out.Flush()
156+
return OK
157+
}
158+
159+
//+ downloadDir + "deis.tar.gz"
160+
161+
func expBackoff(interval time.Duration) time.Duration {
162+
interval = interval * 2
163+
if interval > maxInterval {
164+
interval = maxInterval
165+
}
166+
return interval
167+
}
168+
169+
type serverConfig struct {
170+
server string
171+
}
172+
173+
type Client struct {
174+
Id string
175+
SessionId string
176+
Version string
177+
AppId string
178+
Track string
179+
config *serverConfig
180+
errorRate int
181+
pingsRemaining int
182+
}
183+
184+
func (c *Client) Log(format string, v ...interface{}) {
185+
format = c.Id + ": " + format
186+
fmt.Printf(format, v...)
187+
}
188+
189+
func (c *Client) getCodebaseUrl(uc *omaha.UpdateCheck) string {
190+
return uc.Urls.Urls[0].CodeBase
191+
}
192+
193+
func (c *Client) updateservice() {
194+
fmt.Println("starting systemd units")
195+
files, _ := utils.ListFiles(downloadDir + "*.service")
196+
fmt.Println(files)
197+
198+
}
199+
200+
func (c *Client) downloadFromUrl(url, fileName string) (err error) {
201+
url = url + "deis.tar.gz"
202+
fmt.Printf("Downloading %s to %s", url, fileName)
203+
204+
// TODO: check file existence first with io.IsExist
205+
output, err := os.Create(downloadDir + fileName)
206+
if err != nil {
207+
fmt.Println("Error while creating", fileName, "-", err)
208+
return
209+
}
210+
defer output.Close()
211+
response, err := http.Get(url)
212+
if err != nil {
213+
fmt.Println("Error while downloading", url, "-", err)
214+
return
215+
}
216+
defer response.Body.Close()
217+
218+
n, err := io.Copy(output, response.Body)
219+
if err != nil {
220+
fmt.Println("Error while downloading", url, "-", err)
221+
return
222+
}
223+
224+
fmt.Println(n, "bytes downloaded.")
225+
return
226+
}
227+
228+
func (c *Client) OmahaRequest(otype, result string, updateCheck, isPing bool) *omaha.Request {
229+
req := omaha.NewRequest("lsb", "CoreOS", "", "")
230+
app := req.AddApp(c.AppId, c.Version)
231+
app.MachineID = c.Id
232+
app.BootId = c.SessionId
233+
app.Track = c.Track
234+
app.OEM = instanceFlags.OEM
235+
236+
if updateCheck {
237+
app.AddUpdateCheck()
238+
}
239+
240+
if isPing {
241+
app.AddPing()
242+
app.Ping.LastReportDays = "1"
243+
app.Ping.Status = "1"
244+
}
245+
246+
if otype != "" {
247+
event := app.AddEvent()
248+
event.Type = otype
249+
event.Result = result
250+
if result == "0" {
251+
event.ErrorCode = "2000"
252+
} else {
253+
event.ErrorCode = ""
254+
}
255+
}
256+
257+
return req
258+
}
259+
260+
func (c *Client) MakeRequest(otype, result string, updateCheck, isPing bool) (*omaha.Response, error) {
261+
client := &http.Client{}
262+
req := c.OmahaRequest(otype, result, updateCheck, isPing)
263+
raw, err := xml.MarshalIndent(req, "", " ")
264+
if err != nil {
265+
return nil, err
266+
}
267+
268+
resp, err := client.Post(c.config.server+"/v1/update/", "text/xml", bytes.NewReader(raw))
269+
if err != nil {
270+
return nil, err
271+
}
272+
defer resp.Body.Close()
273+
274+
oresp := new(omaha.Response)
275+
err = xml.NewDecoder(resp.Body).Decode(oresp)
276+
if err != nil {
277+
return nil, err
278+
}
279+
280+
if instanceFlags.verbose {
281+
raw, _ := xml.MarshalIndent(req, "", " ")
282+
c.Log("request: %s\n", string(raw))
283+
raw, _ = xml.MarshalIndent(oresp, "", " ")
284+
c.Log("response: %s\n", string(raw))
285+
}
286+
287+
return oresp, nil
288+
}
289+
290+
func (c *Client) SetVersion(resp *omaha.Response) {
291+
// A field can potentially be nil.
292+
defer func() {
293+
if err := recover(); err != nil {
294+
c.Log("%s: error setting version: %v", c.Id, err)
295+
}
296+
}()
297+
uc := resp.Apps[0].UpdateCheck
298+
url := c.getCodebaseUrl(uc)
299+
c.MakeRequest("13", "1", false, false)
300+
c.downloadFromUrl(url, "deis.tar.gz")
301+
utils.Extract(downloadDir+"deis.tar.gz", downloadDir)
302+
c.MakeRequest("14", "1", false, false)
303+
c.updateservice()
304+
fmt.Println("updated done")
305+
c.MakeRequest("3", "1", false, false)
306+
// installed
307+
fmt.Println("updated done")
308+
// simulate reboot lock for a while
309+
for c.pingsRemaining > 0 {
310+
c.MakeRequest("", "", false, true)
311+
c.pingsRemaining--
312+
time.Sleep(1 * time.Second)
313+
}
314+
315+
c.Log("updated from %s to %s\n", c.Version, uc.Manifest.Version)
316+
317+
c.Version = uc.Manifest.Version
318+
319+
_, err := c.MakeRequest("3", "2", false, false) // Send complete with new version.
320+
if err != nil {
321+
log.Println(err)
322+
}
323+
324+
c.SessionId = uuid.New()
325+
}
326+
327+
// Sleep between n and m seconds
328+
func (c *Client) Loop(n, m int) {
329+
interval := initialInterval
330+
for {
331+
randSleep(n, m)
332+
resp, err := c.MakeRequest("3", "2", true, false)
333+
if err != nil {
334+
log.Println(err)
335+
continue
336+
}
337+
uc := resp.Apps[0].UpdateCheck
338+
if uc.Status != "ok" {
339+
c.Log("update check status: %s\n", uc.Status)
340+
} else {
341+
c.SetVersion(resp)
342+
}
343+
}
344+
}
345+
346+
// Sleeps randomly between n and m seconds.
347+
func randSleep(n, m int) {
348+
r := m
349+
if m-n > 0 {
350+
r = rand.Intn(m-n) + n
351+
}
352+
time.Sleep(time.Duration(r) * time.Second)
353+
}
354+
355+
func instanceDeis(args []string, service *update.Service, out *tabwriter.Writer) int {
356+
if instanceFlags.appId.Get() == nil || instanceFlags.groupId.Get() == nil {
357+
return ERROR_USAGE
358+
}
359+
360+
conf := &serverConfig{
361+
server: globalFlags.Server,
362+
}
363+
364+
c := &Client{
365+
Id: fmt.Sprintf("{update-client-"+utils.NewID(), i),
366+
SessionId: uuid.New(),
367+
Version: instanceFlags.version,
368+
AppId: instanceFlags.appId.String(),
369+
Track: instanceFlags.groupId.String(),
370+
config: conf,
371+
errorRate: instanceFlags.errorRate,
372+
pingsRemaining: instanceFlags.pingOnly,
373+
}
374+
go c.Loop(instanceFlags.minSleep, instanceFlags.maxSleep)
375+
376+
// run forever
377+
wait := make(chan bool)
378+
<-wait
379+
return OK
380+
}

0 commit comments

Comments
 (0)