blob: f42ffc1cc711a678d637feae959681937cbd34c8 [file] [log] [blame]
package metrics
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"time"
appcontext "app/context"
"fidl/fuchsia/cobalt"
)
type releaseChannelUpdater struct {
in chan string
ctx *appcontext.Context
currentChannel *string
}
func startReleaseChannelUpdater(ctx *appcontext.Context) chan string {
cc := readCurrentChannel()
in := make(chan string)
u := releaseChannelUpdater{in: in, ctx: ctx, currentChannel: cc}
go u.run()
return in
}
func (u *releaseChannelUpdater) run() {
var sendResult chan error
var lastSent *string
var pendingSend *string
for {
var toSend *string
select {
case err := <-sendResult:
sendResult = nil
if err != nil {
log.Printf("error sending release channel to cobalt: %v", err)
// will want to retry
toSend = lastSent
}
if pendingSend != nil {
toSend = pendingSend
pendingSend = nil
}
case t := <-u.in:
if sendResult == nil {
// Can send immediately
toSend = &t
} else {
pendingSend = &t
}
}
if toSend != nil {
var fatal error
sendResult, fatal = u.sendAsync(*toSend)
if fatal != nil {
log.Printf("fatal error sending release channel to cobalt: %v; will no longer send release channel updates to cobalt", fatal)
break
}
lastSent = toSend
}
}
// We only get here if something went really wrong
for {
t := <-u.in
log.Printf("not sending channel update to cobalt due to a previous error. New channel: %s", t)
}
}
func (u *releaseChannelUpdater) sendAsync(targetChannel string) (result chan error, fatal error) {
defer func() {
if r := recover(); r != nil {
fatal = fmt.Errorf("%v", r)
}
}()
r, proxy, err := cobalt.NewSystemDataUpdaterWithCtxInterfaceRequest()
if err != nil {
return nil, err
}
ctx.ConnectToEnvService(r)
result = make(chan error)
go func() {
defer proxy.Close()
result <- u.sendSync(proxy, targetChannel)
}()
return
}
func (u *releaseChannelUpdater) sendSync(proxy *cobalt.SystemDataUpdaterWithCtxInterface, targetChannel string) error {
var result cobalt.Status
var err error
if u.currentChannel == nil {
log.Printf("calling cobalt.SetChannel(\"\")")
result, err = proxy.SetChannel(context.Background(), "")
} else {
log.Printf("calling cobalt.SetChannel(%q)", *u.currentChannel)
result, err = proxy.SetChannel(context.Background(), *u.currentChannel)
}
if err != nil {
time.Sleep(5 * time.Second)
// channel broken, return error so we try to reconnect
return err
}
if result == cobalt.StatusEventTooBig {
time.Sleep(5 * time.Second)
// Return an error so we retry
return errors.New("cobalt.SetChannel returned Status.EVENT_TOO_BIG")
}
if result != cobalt.StatusOk {
// Not much we can do about the other status codes but log.
log.Printf("cobalt.SetChannel returned non-OK status: %v", result)
}
return nil
}
type channelMetadataValue struct {
ChannelName string `json:"legacy_amber_source_name"`
}
type channelMetadataFile struct {
Version string `json:"version"`
Content channelMetadataValue `json:"content"`
}
func readCurrentChannel() *string {
path := "/misc/ota/current_channel.json"
f, err := os.Open(path)
if err != nil {
log.Printf("error opening %v: %v", path, err)
return nil
}
defer f.Close()
v := channelMetadataFile{}
err = json.NewDecoder(f).Decode(&v)
if err != nil {
log.Printf("error decoding %v: %v", path, err)
return nil
}
if v.Version != "1" {
log.Printf("unsupported %v version: %v", path, v.Version)
return nil
}
result := v.Content.ChannelName
return &result
}
func writeTargetChannel(targetChannel string) {
err := os.MkdirAll("/misc/ota/", 0700)
if err != nil {
log.Printf("error creating /misc/ota/ directory...: %v", err)
return
}
path := "/misc/ota/target_channel.json"
partPath := path + ".part"
f, err := os.Create(partPath)
if err != nil {
log.Printf("error creating %v: %v", partPath, err)
return
}
defer f.Close()
v := channelMetadataFile{
Version: "1",
Content: channelMetadataValue{
ChannelName: targetChannel,
},
}
if err := json.NewEncoder(f).Encode(&v); err != nil {
log.Printf("error writing %v: %v", path, err)
return
}
f.Sync()
f.Close()
if err := os.Rename(partPath, path); err != nil {
log.Printf("error moving %v to %v: %v", partPath, path, err)
return
}
}