<commit_before>// Copyright (c) 2016 Intel Corporation
//
// 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 main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"sort"
	"testing"
	"time"

	yaml "gopkg.in/yaml.v2"

	"github.com/ciao-project/ciao/ciao-controller/api"
	"github.com/ciao-project/ciao/ciao-controller/types"
	"github.com/ciao-project/ciao/payloads"
	"github.com/ciao-project/ciao/ssntp"
	"github.com/ciao-project/ciao/testutil"
	"github.com/pkg/errors"
)

// ByTenantID is used to sort CNCI instances by Tenant ID.
type ByTenantID []types.CiaoCNCI

func (c ByTenantID) Len() int {
	return len(c)
}

func (c ByTenantID) Swap(i int, j int) {
	c[i], c[j] = c[j], c[i]
}

func (c ByTenantID) Less(i int, j int) bool {
	return c[i].TenantID < c[j].TenantID
}

func testHTTPRequest(t *testing.T, method string, URL string, expectedResponse int, data []byte, validToken bool) []byte {
	req, err := http.NewRequest(method, URL, bytes.NewBuffer(data))
	if err != nil {
		t.Fatal(err)
	}

	req.Header.Set("Content-Type", "application/json")

	tlsConfig := &tls.Config{}

	clientCertFile := "/etc/pki/ciao/auth-admin.pem"
	cert, err := tls.LoadX509KeyPair(clientCertFile, clientCertFile)
	if err != nil {
		t.Fatalf("Unable to load client certiticate: %s", err)
	}

	tlsConfig.Certificates = []tls.Certificate{cert}
	tlsConfig.BuildNameToCertificate()

	transport := &http.Transport{
		TLSClientConfig: tlsConfig,
	}

	client := &http.Client{Transport: transport}
	resp, err := client.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != expectedResponse {
		var msg string

		body, err := ioutil.ReadAll(resp.Body)
		if err == nil {
			msg = string(body)
		}

		t.Fatalf("expected: %d, got: %d, msg: %s", expectedResponse, resp.StatusCode, msg)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}

	return body
}

func testCreateServer(t *testing.T, n int) api.Servers {
	tenant, err := ctl.ds.GetTenant(testutil.ComputeUser)
	if err != nil {
		t.Fatal(err)
	}

	// get a valid workload ID
	wls, err := ctl.ds.GetWorkloads(tenant.ID)
	if err != nil {
		t.Fatal(err)
	}

	if len(wls) == 0 {
		t.Fatalf("No valid workloads for tenant: %s\n", tenant.ID)
	}

	url := testutil.ComputeURL + "/" + tenant.ID + "/instances"

	var server api.CreateServerRequest
	server.Server.MaxInstances = n
	server.Server.WorkloadID = wls[0].ID

	b, err := json.Marshal(server)
	if err != nil {
		t.Fatal(err)
	}

	body := testHTTPRequest(t, "POST", url, http.StatusAccepted, b, true)

	servers := api.Servers{}

	err = json.Unmarshal(body, &servers)
	if err != nil {
		t.Fatal(err)
	}

	if servers.TotalServers != n {
		t.Fatal("Not enough servers returned")
	}

	return servers
}

func testListServerDetailsTenant(t *testing.T, tenantID string) api.Servers {
	url := testutil.ComputeURL + "/" + tenantID + "/instances/detail"

	body := testHTTPRequest(t, "GET", url, http.StatusOK, nil, true)

	s := api.Servers{}
	err := json.Unmarshal(body, &s)
	if err != nil {
		t.Fatal(err)
	}

	return s
}

func TestCreateSingleServer(t *testing.T) {
	_ = testCreateServer(t, 1)
}

func TestListServerDetailsTenant(t *testing.T) {
	tenant, err := ctl.ds.GetTenant(testutil.ComputeUser)
	if err != nil {
		t.Fatal(err)
	}

	servers := testCreateServer(t, 1)
	if servers.TotalServers != 1 {
		t.Fatal(err)
	}

	s := testListServerDetailsTenant(t, tenant.ID)

	if s.TotalServers < 1 {
		t.Fatal("Not enough servers returned")
	}
}

func testShowServerDetails(t *testing.T, httpExpectedStatus int, validToken bool) {
	tenant, err := ctl.ds.GetTenant(testutil.ComputeUser)
	if err != nil {
		t.Fatal(err)
	}

	tURL := testutil.ComputeURL + "/" + tenant.ID + "/instances/"

	servers := testCreateServer(t, 1)
	if servers.TotalServers != 1 {
		t.Fatal(err)
	}

	s := testListServerDetailsTenant(t, tenant.ID)

	if s.TotalServers < 1 {
		t.Fatal("Not enough servers returned")
	}

	for _, s1 := range s.Servers {
		url := tURL + s1.ID

		body := testHTTPRequest(t, "GET", url, httpExpectedStatus, nil, validToken)
		// stop evaluating in case the scenario is InvalidToken
		if httpExpectedStatus == 401 {
			return
		}

		var s2 api.Server
		err = json.Unmarshal(body, &s2)
		if err != nil {
			t.Fatal(err)
		}

		if reflect.DeepEqual(s1, s2.Server) == false {
			t.Fatal("Server details not correct")
			//t.Fatalf("Server details not correct %s %s", s1, s2.Server)
		}
	}
}

func TestShowServerDetails(t *testing.T) {
	testShowServerDetails(t, http.StatusOK, true)
}

func testDeleteServer(t *testing.T, httpExpectedStatus int, httpExpectedErrorStatus int, validToken bool) {
	tenant, err := ctl.ds.GetTenant(testutil.ComputeUser)
	if err != nil {
		t.Fatal(err)
	}

	// instances have to be assigned to a node to be deleted
	client, err := testutil.NewSsntpTestClientConnection("DeleteServer", ssntp.AGENT, testutil.AgentUUID)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Shutdown()

	tURL := testutil.ComputeURL + "/" + tenant.ID + "/instances/"

	servers := testCreateServer(t, 10)
	if servers.TotalServers != 10 {
		t.Fatal(err)
	}

	time.Sleep(2 * time.Second)

	sendStatsCmd(client, t)

	time.Sleep(2 * time.Second)

	s := testListServerDetailsTenant(t, tenant.ID)

	if s.TotalServers < 1 {
		t.Fatal("Not enough servers returned")
	}

	for _, s1 := range s.Servers {
		url := tURL + s1.ID
		if s1.NodeID != "" {
			_ = testHTTPRequest(t, "DELETE", url, httpExpectedStatus, nil, validToken)
		} else {
			_ = testHTTPRequest(t, "DELETE", url, httpExpectedErrorStatus, nil, validToken)
		}
	}
}

func TestDeleteServer(t *testing.T) {
	testDeleteServer(t, http.StatusNoContent, http.StatusForbidden, true)
}

func testServersActionStart(t *testing.T, httpExpectedStatus int, validToken bool) {
	tenant, err := ctl.ds.GetTenant(testutil.ComputeUser)
	if err != nil {
		t.Fatal(err)
	}

	url := testutil.ComputeURL + "/v2.1/" + tenant.ID + "/servers/action"

	client, err := testutil.NewSsntpTestClientConnection("ServersActionStart", ssntp.AGENT, testutil.AgentUUID)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Shutdown()

	servers := testCreateServer(t, 1)
	if servers.TotalServers != 1 {
		t.Fatal(err)
	}

	time.Sleep(2 * time.Second)

	sendStatsCmd(client, t)

	time.Sleep(1 * time.Second)

	err = ctl.stopInstance(servers.Servers[0].ID)
	if err != nil {
		t.Fatal(err)
	}

	time.Sleep(1 * time.Second)

	sendStatsCmd(client, t)

	time.Sleep(1 * time.Second)

	var ids []string<commit_msg>ciao-controller: Fix testServersActionStart to wait for DELETE event<commit_after>// Copyright (c) 2016 Intel Corporation
//
// 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 main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"sort"
	"testing"
	"time"

	yaml "gopkg.in/yaml.v2"

	"github.com/ciao-project/ciao/ciao-controller/api"
	"github.com/ciao-project/ciao/ciao-controller/types"
	"github.com/ciao-project/ciao/payloads"
	"github.com/ciao-project/ciao/ssntp"
	"github.com/ciao-project/ciao/testutil"
	"github.com/pkg/errors"
)

// ByTenantID is used to sort CNCI instances by Tenant ID.
type ByTenantID []types.CiaoCNCI

func (c ByTenantID) Len() int {
	return len(c)
}

func (c ByTenantID) Swap(i int, j int) {
	c[i], c[j] = c[j], c[i]
}

func (c ByTenantID) Less(i int, j int) bool {
	return c[i].TenantID < c[j].TenantID
}

func testHTTPRequest(t *testing.T, method string, URL string, expectedResponse int, data []byte, validToken bool) []byte {
	req, err := http.NewRequest(method, URL, bytes.NewBuffer(data))
	if err != nil {
		t.Fatal(err)
	}

	req.Header.Set("Content-Type", "application/json")

	tlsConfig := &tls.Config{}

	clientCertFile := "/etc/pki/ciao/auth-admin.pem"
	cert, err := tls.LoadX509KeyPair(clientCertFile, clientCertFile)
	if err != nil {
		t.Fatalf("Unable to load client certiticate: %s", err)
	}

	tlsConfig.Certificates = []tls.Certificate{cert}
	tlsConfig.BuildNameToCertificate()

	transport := &http.Transport{
		TLSClientConfig: tlsConfig,
	}

	client := &http.Client{Transport: transport}
	resp, err := client.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != expectedResponse {
		var msg string

		body, err := ioutil.ReadAll(resp.Body)
		if err == nil {
			msg = string(body)
		}

		t.Fatalf("expected: %d, got: %d, msg: %s", expectedResponse, resp.StatusCode, msg)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}

	return body
}

func testCreateServer(t *testing.T, n int) api.Servers {
	tenant, err := ctl.ds.GetTenant(testutil.ComputeUser)
	if err != nil {
		t.Fatal(err)
	}

	// get a valid workload ID
	wls, err := ctl.ds.GetWorkloads(tenant.ID)
	if err != nil {
		t.Fatal(err)
	}

	if len(wls) == 0 {
		t.Fatalf("No valid workloads for tenant: %s\n", tenant.ID)
	}

	url := testutil.ComputeURL + "/" + tenant.ID + "/instances"

	var server api.CreateServerRequest
	server.Server.MaxInstances = n
	server.Server.WorkloadID = wls[0].ID

	b, err := json.Marshal(server)
	if err != nil {
		t.Fatal(err)
	}

	body := testHTTPRequest(t, "POST", url, http.StatusAccepted, b, true)

	servers := api.Servers{}

	err = json.Unmarshal(body, &servers)
	if err != nil {
		t.Fatal(err)
	}

	if servers.TotalServers != n {
		t.Fatal("Not enough servers returned")
	}

	return servers
}

func testListServerDetailsTenant(t *testing.T, tenantID string) api.Servers {
	url := testutil.ComputeURL + "/" + tenantID + "/instances/detail"

	body := testHTTPRequest(t, "GET", url, http.StatusOK, nil, true)

	s := api.Servers{}
	err := json.Unmarshal(body, &s)
	if err != nil {
		t.Fatal(err)
	}

	return s
}

func TestCreateSingleServer(t *testing.T) {
	_ = testCreateServer(t, 1)
}

func TestListServerDetailsTenant(t *testing.T) {
	tenant, err := ctl.ds.GetTenant(testutil.ComputeUser)
	if err != nil {
		t.Fatal(err)
	}

	servers := testCreateServer(t, 1)
	if servers.TotalServers != 1 {
		t.Fatal(err)
	}

	s := testListServerDetailsTenant(t, tenant.ID)

	if s.TotalServers < 1 {
		t.Fatal("Not enough servers returned")
	}
}

func testShowServerDetails(t *testing.T, httpExpectedStatus int, validToken bool) {
	tenant, err := ctl.ds.GetTenant(testutil.ComputeUser)
	if err != nil {
		t.Fatal(err)
	}

	tURL := testutil.ComputeURL + "/" + tenant.ID + "/instances/"

	servers := testCreateServer(t, 1)
	if servers.TotalServers != 1 {
		t.Fatal(err)
	}

	s := testListServerDetailsTenant(t, tenant.ID)

	if s.TotalServers < 1 {
		t.Fatal("Not enough servers returned")
	}

	for _, s1 := range s.Servers {
		url := tURL + s1.ID

		body := testHTTPRequest(t, "GET", url, httpExpectedStatus, nil, validToken)
		// stop evaluating in case the scenario is InvalidToken
		if httpExpectedStatus == 401 {
			return
		}

		var s2 api.Server
		err = json.Unmarshal(body, &s2)
		if err != nil {
			t.Fatal(err)
		}

		if reflect.DeepEqual(s1, s2.Server) == false {
			t.Fatal("Server details not correct")
			//t.Fatalf("Server details not correct %s %s", s1, s2.Server)
		}
	}
}

func TestShowServerDetails(t *testing.T) {
	testShowServerDetails(t, http.StatusOK, true)
}

func testDeleteServer(t *testing.T, httpExpectedStatus int, httpExpectedErrorStatus int, validToken bool) {
	tenant, err := ctl.ds.GetTenant(testutil.ComputeUser)
	if err != nil {
		t.Fatal(err)
	}

	// instances have to be assigned to a node to be deleted
	client, err := testutil.NewSsntpTestClientConnection("DeleteServer", ssntp.AGENT, testutil.AgentUUID)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Shutdown()

	tURL := testutil.ComputeURL + "/" + tenant.ID + "/instances/"

	servers := testCreateServer(t, 10)
	if servers.TotalServers != 10 {
		t.Fatal(err)
	}

	time.Sleep(2 * time.Second)

	sendStatsCmd(client, t)

	time.Sleep(2 * time.Second)

	s := testListServerDetailsTenant(t, tenant.ID)

	if s.TotalServers < 1 {
		t.Fatal("Not enough servers returned")
	}

	for _, s1 := range s.Servers {
		url := tURL + s1.ID
		if s1.NodeID != "" {
			_ = testHTTPRequest(t, "DELETE", url, httpExpectedStatus, nil, validToken)
		} else {
			_ = testHTTPRequest(t, "DELETE", url, httpExpectedErrorStatus, nil, validToken)
		}
	}
}

func TestDeleteServer(t *testing.T) {
	testDeleteServer(t, http.StatusNoContent, http.StatusForbidden, true)
}

func testServersActionStart(t *testing.T, httpExpectedStatus int, validToken bool) {
	tenant, err := ctl.ds.GetTenant(testutil.ComputeUser)
	if err != nil {
		t.Fatal(err)
	}

	url := testutil.ComputeURL + "/v2.1/" + tenant.ID + "/servers/action"

	client, err := testutil.NewSsntpTestClientConnection("ServersActionStart", ssntp.AGENT, testutil.AgentUUID)
	if err != nil {
		t.Fatal(err)
	}
	defer client.Shutdown()

	servers := testCreateServer(t, 1)
	if servers.TotalServers != 1 {
		t.Fatal(err)
	}

	time.Sleep(2 * time.Second)

	sendStatsCmd(client, t)

	time.Sleep(1 * time.Second)

	serverCh := server.AddCmdChan(ssntp.DELETE)

	err = ctl.stopInstance(servers.Servers[0].ID)
	if err != nil {
		t.Fatal(err)
	}

	err = sendStopEvent(client, servers.Servers[0].ID)
	if err != nil {
		t.Fatal(err)
	}

	_, err = server.GetCmdChanResult(serverCh, ssntp.DELETE)
	if err != nil {
		t.Fatal(err)
	}

	var ids []string
{'fraction_non_alphanumeric': 0.14401010597234892, 'fraction_numerical': 0.007088216716962594, 'mean_word_length': 3.832146490335707, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3689517', 'n_tokens_mistral': 5582, 'n_tokens_neox': 5287, 'n_words': 1268}