Skip to content

Commit

Permalink
Merge pull request #264 from SUSE/include_uptime_tracker
Browse files Browse the repository at this point in the history
Integrating uptime-tracker ( https://github.com/SUSE/uptime-tracker/ )
  • Loading branch information
ngetahun authored Sep 26, 2024
2 parents 458b67a + d6af6e7 commit 1a38f57
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 4 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dist: clean internal/connect/version.txt vendor
@cp LICENSE LICENSE.LGPL README.md $(DIST)
@cp SUSEConnect.example $(DIST)
@cp build/packaging/suseconnect-keepalive* $(DIST)/build/packaging
@cp build/packaging/suse-uptime-tracker* $(DIST)/build/packaging
@cp -r build/packaging/suseconnect-ng* $(DIST)

@tar cfvj vendor.tar.xz vendor
Expand All @@ -52,6 +53,7 @@ build: clean out internal/connect/version.txt
$(GO) build $(GOFLAGS) $(BINFLAGS) $(OUT) github.com/SUSE/connect-ng/cmd/suseconnect
$(GO) build $(GOFLAGS) $(BINFLAGS) $(OUT) github.com/SUSE/connect-ng/cmd/zypper-migration
$(GO) build $(GOFLAGS) $(BINFLAGS) $(OUT) github.com/SUSE/connect-ng/cmd/zypper-search-packages
$(GO) build $(GOFLAGS) $(BINFLAGS) $(OUT) github.com/SUSE/connect-ng/cmd/suse-uptime-tracker
$(GO) build $(GOFLAGS) $(SOFLAGS) $(OUT) github.com/SUSE/connect-ng/third_party/libsuseconnect

# This "arm" means ARM64v8 little endian, the one being delivered currently on
Expand Down
7 changes: 7 additions & 0 deletions build/packaging/suse-uptime-tracker.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[Unit]
Description=Run SUSE uptime tracker
Wants=suse-uptime-tracker.timer

[Service]
Type=oneshot
ExecStart=/usr/bin/suse-uptime-tracker
10 changes: 10 additions & 0 deletions build/packaging/suse-uptime-tracker.timer
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Unit]
Description=Schedule uptime tracking every 15 minutes

[Timer]
# Run this timer every 15 minutes
OnCalendar=*:0/15
RandomizedDelaySec=5m

[Install]
WantedBy=timers.target
1 change: 1 addition & 0 deletions build/packaging/suseconnect-ng.changes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Fri Sep 13 15:56:05 UTC 2024 - Miquel Sabate Sola <[email protected]>

- IN PROGRESS: 1.13
- Integrating uptime-tracker

-------------------------------------------------------------------
Fri Sep 13 14:11:22 UTC 2024 - Miquel Sabate Sola <[email protected]>
Expand Down
17 changes: 13 additions & 4 deletions build/packaging/suseconnect-ng.spec
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ echo %{version} > internal/connect/version.txt
go build -v -ldflags "-s -w" -mod=vendor -buildmode=pie -o bin/suseconnect %{project}/cmd/suseconnect
go build -v -ldflags "-s -w" -mod=vendor -buildmode=pie -o bin/zypper-migration %{project}/cmd/zypper-migration
go build -v -ldflags "-s -w" -mod=vendor -buildmode=pie -o bin/zypper-search-packages %{project}/cmd/zypper-search-packages
go build -v -ldflags "-s -w" -mod=vendor -buildmode=pie -o bin/suse-uptime-tracker %{project}/cmd/suse-uptime-tracker

# the library
mkdir -p %_builddir/go/lib
Expand All @@ -115,6 +116,7 @@ go build -v -ldflags "-s -w" -mod=vendor -buildmode=c-shared -o lib/libsuseconne
%install
# Install binary + symlinks
install -D -m 0755 bin/suseconnect %{buildroot}/%{_bindir}/suseconnect
install -D -m 0755 bin/suse-uptime-tracker %{buildroot}/%{_bindir}/suse-uptime-tracker
ln -s %{_bindir}/suseconnect %{buildroot}/%{_bindir}/SUSEConnect

install -d -m 0755 %{buildroot}/%{_sbindir}
Expand All @@ -139,13 +141,16 @@ install -D -m 644 SUSEConnect.example %{buildroot}%{_sysconfdir}/SUSEConnect.exa
# Install the SUSEConnect --keepalive timer and service.
install -D -m 644 build/packaging/suseconnect-keepalive.timer %{buildroot}/%{_unitdir}/suseconnect-keepalive.timer
install -D -m 644 build/packaging/suseconnect-keepalive.service %{buildroot}/%{_unitdir}/suseconnect-keepalive.service
install -D -m 644 build/packaging/suse-uptime-tracker.timer %{buildroot}/%{_unitdir}/suse-uptime-tracker.timer
install -D -m 644 build/packaging/suse-uptime-tracker.service %{buildroot}/%{_unitdir}/suse-uptime-tracker.service
ln -sf service %{buildroot}/%{_sbindir}/rcsuseconnect-keepalive
ln -sf service %{buildroot}/%{_sbindir}/rcsuse-uptime-tracker

# we currently do not ship the source for any go module
rm -rf %{buildroot}/usr/share/go

%pre
%service_add_pre suseconnect-keepalive.service suseconnect-keepalive.timer
%service_add_pre suseconnect-keepalive.service suseconnect-keepalive.timer suse-uptime-tracker.service suse-uptime-tracker.timer

# in pre blocks the old version is still installed. This way we can detect
# if --keepalive was already present before
Expand Down Expand Up @@ -197,13 +202,13 @@ fi
sed -i '/RandomizedDelaySec*/d' %{_unitdir}/suseconnect-keepalive.timer
sed -i "s/OnCalendar=daily/OnCalendar=*-*-* $TIMER_HOUR:$TIMER_MINUTE:00/" %{_unitdir}/suseconnect-keepalive.timer
%endif
%service_add_post suseconnect-keepalive.service suseconnect-keepalive.timer
%service_add_post suseconnect-keepalive.service suseconnect-keepalive.timer suse-uptime-tracker.service suse-uptime-tracker.timer

%preun
%service_del_preun suseconnect-keepalive.service suseconnect-keepalive.timer
%service_del_preun suseconnect-keepalive.service suseconnect-keepalive.timer suse-uptime-tracker.service suse-uptime-tracker.timer

%postun
%service_del_postun suseconnect-keepalive.service suseconnect-keepalive.timer
%service_del_postun suseconnect-keepalive.service suseconnect-keepalive.timer suse-uptime-tracker.service suse-uptime-tracker.timer

%posttrans
if [ -e /run/suseconnect-keepalive.timer.is-enabled ]; then
Expand All @@ -219,15 +224,19 @@ fi
%license LICENSE LICENSE.LGPL
%doc README.md
%{_bindir}/suseconnect
%{_bindir}/suse-uptime-tracker
%{_bindir}/SUSEConnect
%{_sbindir}/SUSEConnect
%{_sbindir}/rcsuseconnect-keepalive
%{_sbindir}/rcsuse-uptime-tracker
/usr/lib/zypper/commands
%{_mandir}/man8/*
%{_mandir}/man5/*
%{_unitdir}/suseconnect-keepalive.service
%{_unitdir}/suseconnect-keepalive.timer
%config %{_sysconfdir}/SUSEConnect.example
%{_unitdir}/suse-uptime-tracker.service
%{_unitdir}/suse-uptime-tracker.timer

%files -n libsuseconnect
%license LICENSE LICENSE.LGPL
Expand Down
6 changes: 6 additions & 0 deletions cmd/suse-uptime-tracker/uptimeTrackerUsage.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Usage: suse-uptime-tracker [options]
Keep track of system uptime. If no options are specified, it will update
the uptime tracking log file with the current uptime.

--version Print program version.
-h, --help Show this message.
152 changes: 152 additions & 0 deletions cmd/suse-uptime-tracker/uptime_tracker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package main

import (
"bufio"
_ "embed"
"errors"
"flag"
"fmt"
"os"
"sort"
"strings"
"time"
)

var (
//go:embed version.txt
version string
//go:embed uptimeTrackerUsage.txt
uptimeTrackerUsageText string
)

const (
uptimeCheckLogsFilePath = "/etc/zypp/suse-uptime.log"
dateStringFormat = "2006-01-02"
initUptimeHours = "000000000000000000000000" // initialize the uptime hours bit string with
daysBeforePurge = 90 // purge all the records after this many days
)

func getShortenedVersion() string {
return strings.Split(strings.TrimSpace(version), "~")[0]
}

func exitOnError(err error) {
if err == nil {
return
}
fmt.Println(err)
os.Exit(1)
}

func displayUptimeVersion() {
var (
version bool
)

flag.Usage = func() {
fmt.Print(uptimeTrackerUsageText)
}

flag.BoolVar(&version, "version", false, "")

flag.Parse()
if version {
fmt.Println(getShortenedVersion())
os.Exit(0)
}
}

func readUptimeLogFile(uptimeLogsFilePath string) (map[string]string, error) {
uptimeLogsFile, err := os.Open(uptimeLogsFilePath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
// file doesn't exist, so don't error out
return nil, nil
}
return nil, err
}
fileScanner := bufio.NewScanner(uptimeLogsFile)
fileScanner.Split(bufio.ScanLines)
var logEntries = make(map[string]string)

var entry []string
for fileScanner.Scan() {
entryText := fileScanner.Text()
entry = strings.Split(entryText, ":")
if len(entry) != 2 {
return nil, errors.New("Uptime log file is corrupted. Invalid log entry " + entryText)
}
logEntries[entry[0]] = entry[1]
}
err = uptimeLogsFile.Close()
if err != nil {
return nil, err
}
return logEntries, nil
}

func purgeOldUptimeLog(uptimeLogs map[string]string) (map[string]string, error) {
now := time.Now().UTC()
purgeBefore := now.AddDate(0, 0, -daysBeforePurge)
var purgedLogs = make(map[string]string)
for day, uptimeHours := range uptimeLogs {
timestamp, err := time.Parse(dateStringFormat, day)
if err != nil {
return nil, err
}
if timestamp.After(purgeBefore) {
purgedLogs[day] = uptimeHours
}
}
return purgedLogs, nil
}

func updateUptimeLog(uptimeLogs map[string]string) map[string]string {
// NOTE: we are standardizing timezone to UTC
now := time.Now().UTC()
day := now.Format(dateStringFormat)
hours, _, _ := now.Clock()
_, ok := uptimeLogs[day]
if !ok {
uptimeLogs[day] = initUptimeHours
}
uptimeHoursMap := []rune(uptimeLogs[day])
uptimeHoursMap[hours] = '1'
uptimeLogs[day] = string(uptimeHoursMap)

return uptimeLogs
}

func writeUptimeLogsFile(uptimeLogsFilePath string, uptimeLogs map[string]string) error {
uptimeLogsFile, err := os.OpenFile(uptimeLogsFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer uptimeLogsFile.Close()

// sort the keys
keys := make([]string, 0, len(uptimeLogs))
for day := range uptimeLogs {
keys = append(keys, day)
}
sort.Strings(keys)

for _, day := range keys {
_, err = uptimeLogsFile.WriteString(day + ":" + uptimeLogs[day] + "\n")
if err != nil {
return err
}
}
return nil
}

func main() {
displayUptimeVersion()
uptimeLogs, err := readUptimeLogFile(uptimeCheckLogsFilePath)
exitOnError(err)
uptimeLogs, err = purgeOldUptimeLog(uptimeLogs)
exitOnError(err)
uptimeLogs = updateUptimeLog(uptimeLogs)
err = writeUptimeLogsFile(uptimeCheckLogsFilePath, uptimeLogs)
exitOnError(err)
}
92 changes: 92 additions & 0 deletions cmd/suse-uptime-tracker/uptime_tracker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package main

import (
"io/ioutil"
"os"
"testing"
"time"

"github.com/google/uuid"
)

const (
DDMMYYYY = "2006-01-02"
)

func createTestUptimeLogFileWithContent(content string) (string, error) {
tempFile, err := ioutil.TempFile("", "testUptimeLog")
if err != nil {
return "", err
}
tempFilePath := tempFile.Name()
if _, err := tempFile.WriteString(content); err != nil {
os.Remove(tempFilePath)
return "", err
}
if err := tempFile.Close(); err != nil {
os.Remove(tempFilePath)
return "", err
}

return tempFilePath, nil
}

func TestUptimeLogFileDoesNotExist(t *testing.T) {
bogusUptimeLogsFilePath := uuid.New().String()
uptimeLog, err := readUptimeLogFile(bogusUptimeLogsFilePath)
if uptimeLog != nil || err != nil {
t.Fatalf("Expected err and uptimeLog to be nil if uptime log file does not exist")
}
}

func TestCorruptedUptimeLog(t *testing.T) {
corruptedUptimeLog := `2024-01-18:000000000000001000110000
2024-01-13000000000000000000010000`
tempFilePath, err := createTestUptimeLogFileWithContent(corruptedUptimeLog)
if err != nil {
t.Fatalf("Failed to create temp uptime log file for testing")
}
_, err = readUptimeLogFile(tempFilePath)
if err == nil {
t.Fatalf("Expected an error for corrupted uptime logs entry")
}
defer os.Remove(tempFilePath)
}

func TestPurgeOldUptimeLog(t *testing.T) {
datetime := time.Now().UTC()
currdate := string((datetime.Format(DDMMYYYY)))
olddatetime := datetime.AddDate(-1, 0, 0)
olddate := string((olddatetime.Format(DDMMYYYY)))
PurgeOldUptimeLog := currdate + ":000000000000001000110000\n" + olddate + ":000000000000000000010000\n"
tempFilePath, err := createTestUptimeLogFileWithContent(PurgeOldUptimeLog)
if err != nil {
t.Fatalf("Failed to populate old uptime logs content for testing")
}
uptimelog, _ := readUptimeLogFile(tempFilePath)
purgelog, _ := purgeOldUptimeLog(uptimelog)
if len(purgelog) != 1 {
t.Fatalf("Failed to purge old uptime logs entry")
}
defer os.Remove(tempFilePath)
}

func TestUpdateuptimeLog(t *testing.T) {
datetime := time.Now().UTC()
hour, _, _ := datetime.Clock()
strhour := rune(hour)
currdate := string((datetime.Format(DDMMYYYY)))
PopulateUptimeLog := currdate + ":000000000000000000000000\n"
tempFilePath, err := createTestUptimeLogFileWithContent(PopulateUptimeLog)
if err != nil {
t.Fatalf("Failed to populate uptime logs content for testing")
}
uptimelog, _ := readUptimeLogFile(tempFilePath)
uptimelog = updateUptimeLog(uptimelog)
timeupd := string(uptimelog[currdate])
activehr := timeupd[strhour : strhour+1]
if activehr != "1" {
t.Fatalf("Failed to update uptime hour ")
}
defer os.Remove(tempFilePath)
}
1 change: 1 addition & 0 deletions cmd/suse-uptime-tracker/version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0

0 comments on commit 1a38f57

Please sign in to comment.