Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test CheckForServiceAvailability #25

Merged
merged 9 commits into from
Mar 28, 2024
Merged
121 changes: 87 additions & 34 deletions datasetUtils/checkForServiceAvailability.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import (
"log"
"net/http"
"os"

"gopkg.in/yaml.v2"

"github.com/fatih/color"
)

Expand All @@ -28,48 +26,66 @@ type ServiceAvailability struct {
Qa OverallAvailability
}

var GitHubMainLocation = "https://raw.githubusercontent.com/paulscherrerinstitute/scicat-cli/main"

// CheckForServiceAvailability checks the availability of the dataset ingestor service.
// It fetches a YAML file from GitHubMainLocation, parses it, and logs the service availability status.
func CheckForServiceAvailability(client *http.Client, testenvFlag bool, autoarchiveFlag bool) {
resp, err := client.Get(DeployLocation + "datasetIngestorServiceAvailability.yml")
s, err := getServiceAvailability(client)
if err != nil {
fmt.Println("No Information about Service Availability")
log.Printf("Failed to get service availability: %v", err)
return
kavir1698 marked this conversation as resolved.
Show resolved Hide resolved
}
defer resp.Body.Close()

if resp.StatusCode != 200 {
log.Println("No Information about Service Availability")
log.Printf("Error: Got %s fetching %s\n", resp.Status, DeployLocation + "datasetIngestorServiceAvailability.yml")
return

status, env := determineStatusAndEnv(s, testenvFlag)

logPlannedDowntime(status, env)

err = handleServiceUnavailability(status, env, autoarchiveFlag)
if err != nil {
log.Printf("Error: %v", err)
os.Exit(1)
}
}

yamlFile, err := io.ReadAll(resp.Body)
func getServiceAvailability(client *http.Client) (ServiceAvailability, error) {
yamlFile, err := readYAMLFile(client)
if err != nil {
fmt.Println("Can not read service availability file for this application")
return
return ServiceAvailability{}, err
}

s := ServiceAvailability{}
err = yaml.Unmarshal(yamlFile, &s)
if err != nil {
log.Fatalf("Unmarshal of availabilty file failed: %v\n%s", err, yamlFile)
return ServiceAvailability{}, fmt.Errorf("Unmarshal of availabilty file failed: %v\n%s", err, yamlFile)
}
var status OverallAvailability
var env string
// define default value
status = OverallAvailability{Availability{"on", "", "", ""}, Availability{"on", "", "", ""}}

return s, nil
}

func determineStatusAndEnv(s ServiceAvailability, testenvFlag bool) (OverallAvailability, string) {
status := OverallAvailability{Availability{"on", "", "", ""}, Availability{"on", "", "", ""}}
env := "production"

if testenvFlag {
if (OverallAvailability{}) != s.Qa {
status = s.Qa
}
env = "test"
} else {
} else {
if (OverallAvailability{}) != s.Production {
status = s.Production
}
env = "production"
status = s.Production
}
}
defer color.Unset()

return status, env
}

func logPlannedDowntime(status OverallAvailability, env string) {
// Reset the terminal color after the function returns
defer color.Unset()

// Log the planned downtime for the ingest and archive services, if any
if status.Ingest.Downfrom != "" {
color.Set(color.FgYellow)
fmt.Printf("Next planned downtime for %s data catalog ingest service is scheduled at %v\n", env, status.Ingest.Downfrom)
Expand All @@ -86,18 +102,55 @@ func CheckForServiceAvailability(client *http.Client, testenvFlag bool, autoarch
color.Set(color.FgYellow)
fmt.Printf("It is scheduled to last until %v\n", status.Archive.Downto)
}
}

func handleServiceUnavailability(status OverallAvailability, env string, autoarchiveFlag bool) error {
// If the ingest service is not available, log a message and return an error
if status.Ingest.Status != "on" {
color.Set(color.FgRed)
log.Printf("The %s data catalog is currently not available for ingesting new datasets\n", env)
log.Printf("Planned downtime until %v. Reason: %s\n", status.Ingest.Downto, status.Ingest.Comment)
color.Unset()
os.Exit(1)
logServiceUnavailability("ingest", env, status.Ingest)
return fmt.Errorf("ingest service is unavailable")
}

// If the archive service is not available and autoarchiveFlag is set, log a message and return an error
if autoarchiveFlag && status.Archive.Status != "on" {
color.Set(color.FgRed)
log.Printf("The %s data catalog is currently not available for archiving new datasets\n", env)
log.Printf("Planned downtime until %v. Reason: %s\n", status.Archive.Downto, status.Archive.Comment)
color.Unset()
os.Exit(1)
logServiceUnavailability("archive", env, status.Archive)
return fmt.Errorf("archive service is unavailable")
}

return nil
}

func logServiceUnavailability(serviceName string, env string, availability Availability) {
color.Set(color.FgRed)
log.Printf("The %s data catalog is currently not available for %sing new datasets\n", env, serviceName)
log.Printf("Planned downtime until %v. Reason: %s\n", availability.Downto, availability.Comment)
color.Unset()
}

func readYAMLFile(client *http.Client) ([]byte, error) {
// Construct the URL of the service availability YAML file
yamlURL := fmt.Sprintf("%s/cmd/datasetIngestor/datasetIngestorServiceAvailability.yml", GitHubMainLocation)

// Send a GET request to fetch the service availability YAML file
resp, err := client.Get(yamlURL)
if err != nil {
fmt.Println("No Information about Service Availability")
return nil, fmt.Errorf("failed to fetch the service availability YAML file: %w", err)
}
defer resp.Body.Close()

// If the HTTP status code is not 200 (OK), log a message and return
if resp.StatusCode != 200 {
log.Println("No Information about Service Availability")
log.Printf("Error: Got %s fetching %s\n", resp.Status, yamlURL)
return nil, fmt.Errorf("got %s fetching %s", resp.Status, yamlURL)
}

// Read the entire body of the response (the YAML file)
yamlFile, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("Can not read service availability file for this application")
}

return yamlFile, nil
}
219 changes: 219 additions & 0 deletions datasetUtils/checkForServiceAvailability_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package datasetUtils

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"bytes"
"gopkg.in/yaml.v2"
"log"
"os"
"strings"
"reflect"
)

func TestReadYAMLFile(t *testing.T) {
// Create a mock HTTP server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/repos/paulscherrerinstitute/scicat-cli/releases/latest":
// Respond with a mock release
fmt.Fprintln(w, `{"tag_name": "v1.0.0"}`)
case "/paulscherrerinstitute/scicat-cli/releases/download/v1.0.0/cmd/datasetIngestor/datasetIngestorServiceAvailability.yml":
// Respond with a mock YAML file
fmt.Fprintln(w, "mock: YAML file")
default:
http.NotFound(w, r)
}
}))
defer server.Close()

// Update the GitHubAPI and GitHubMainLocation variables to point to the mock server
oldGitHubAPI := GitHubAPI
oldGitHubMainLocation := GitHubMainLocation
GitHubAPI = server.URL + "/repos/paulscherrerinstitute/scicat-cli/releases/latest"
GitHubMainLocation = server.URL + "/paulscherrerinstitute/scicat-cli/releases/download/v1.0.0"
defer func() {
// Restore the original variables after the test
GitHubAPI = oldGitHubAPI
GitHubMainLocation = oldGitHubMainLocation
}()

// Create a new HTTP client
client := &http.Client{}

// Call the function
yamlFile, err := readYAMLFile(client)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}

// Check that the function returned the expected YAML file
expected := []byte("mock: YAML file\n")
if !bytes.Equal(yamlFile, expected) {
t.Errorf("Expected %q, got %q", expected, yamlFile)
}
}

func TestReadYAMLFileIntegration(t *testing.T) {
// Create a new HTTP client
client := &http.Client{}

// Call the function
yamlFile, err := readYAMLFile(client)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}

// Check that the function returned a non-empty file
if len(yamlFile) == 0 {
t.Errorf("Expected a non-empty YAML file, got an empty file")
}
}

func TestYAMLStructure(t *testing.T) {
// The test will fail if the indentation of yamlFile is not correct
yamlFile := []byte(`
production:
ingest:
status: on
archive:
status: on
qa:
ingest:
status: on
archive:
status: on
`)

var serviceAvailability ServiceAvailability

err := yaml.Unmarshal(yamlFile, &serviceAvailability)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}

checkService := func(service Availability, serviceName string) {
if service.Status != "on" {
if service.Downfrom == "" {
t.Errorf("Expected 'downfrom' for %s when status is 'down'", serviceName)
}

if service.Downto == "" {
t.Errorf("Expected 'downto' for %s when status is 'down'", serviceName)
}

if service.Comment == "" {
t.Errorf("Expected 'comment' for %s when status is 'down'", serviceName)
}
}
}

checkService(serviceAvailability.Production.Ingest, "production ingest")
checkService(serviceAvailability.Production.Archive, "production archive")
checkService(serviceAvailability.Qa.Ingest, "qa ingest")
checkService(serviceAvailability.Qa.Archive, "qa archive")
}

func TestLogServiceUnavailability(t *testing.T) {
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()

serviceName := "ingest"
env := "test"
availability := Availability{
Status: "off",
Downfrom: "2022-01-01T00:00:00Z",
Downto: "2022-01-02T00:00:00Z",
Comment: "Maintenance",
}

logServiceUnavailability(serviceName, env, availability)

want := "The test data catalog is currently not available for ingesting new datasets"
if got := buf.String(); !strings.Contains(got, want) {
t.Errorf("logServiceUnavailability() = %q, want %q", got, want)
}

want = "Planned downtime until 2022-01-02T00:00:00Z. Reason: Maintenance"
if got := buf.String(); !strings.Contains(got, want) {
t.Errorf("logServiceUnavailability() = %q, want %q", got, want)
}
}

func TestHandleServiceUnavailability(t *testing.T) {
tests := []struct {
name string
status OverallAvailability
env string
autoarchiveFlag bool
wantErr bool
}{
{
name: "ingest service unavailable",
status: OverallAvailability{
Ingest: Availability{Status: "off"},
Archive: Availability{Status: "on"},
},
env: "test",
autoarchiveFlag: false,
wantErr: true,
},
// Add more test cases here.
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := handleServiceUnavailability(tt.status, tt.env, tt.autoarchiveFlag)
if (err != nil) != tt.wantErr {
t.Errorf("handleServiceUnavailability() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestDetermineStatusAndEnv(t *testing.T) {
tests := []struct {
name string
s ServiceAvailability
testenvFlag bool
wantStatus OverallAvailability
wantEnv string
}{
{
name: "test environment",
s: ServiceAvailability{
Qa: OverallAvailability{
Ingest: Availability{Status: "off"},
Archive: Availability{Status: "on"},
},
Production: OverallAvailability{
Ingest: Availability{Status: "on"},
Archive: Availability{Status: "on"},
},
},
testenvFlag: true,
wantStatus: OverallAvailability{
Ingest: Availability{Status: "off"},
Archive: Availability{Status: "on"},
},
wantEnv: "test",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotStatus, gotEnv := determineStatusAndEnv(tt.s, tt.testenvFlag)
if !reflect.DeepEqual(gotStatus, tt.wantStatus) {
t.Errorf("determineStatusAndEnv() gotStatus = %v, want %v", gotStatus, tt.wantStatus)
}
if gotEnv != tt.wantEnv {
t.Errorf("determineStatusAndEnv() gotEnv = %v, want %v", gotEnv, tt.wantEnv)
}
})
}
}
Loading