// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 devicemapper

import (
	"fmt"
	"sync"
	"testing"
	"time"

	"github.com/google/cadvisor/devicemapper/fake"
)

func TestRefresh(t *testing.T) {
	usage := map[string]uint64{
		"1": 12345,
		"2": 23456,
		"3": 34567,
	}

	cases := []struct {
		name            string
		dmsetupCommands []fake.DmsetupCommand
		thinLsOutput    map[string]uint64
		thinLsErr       error
		expectedError   bool
		deviceID        string
		expectedUsage   uint64
	}{
		{
			name: "check reservation fails",
			dmsetupCommands: []fake.DmsetupCommand{
				{Name: "status", Result: "", Err: fmt.Errorf("not gonna work")},
			},
			expectedError: true,
		},
		{
			name: "no existing reservation - ok with minimum # of fields",
			dmsetupCommands: []fake.DmsetupCommand{
				{Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 -", Err: nil}, // status check
				{Name: "message", Result: "", Err: nil},                                                 // make reservation
				{Name: "message", Result: "", Err: nil},                                                 // release reservation
			},
			thinLsOutput:  usage,
			expectedError: false,
			deviceID:      "2",
			expectedUsage: 23456,
		},
		{
			name: "no existing reservation - ok",
			dmsetupCommands: []fake.DmsetupCommand{
				{Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 - rw no_discard_passdown error_if_no_space - ", Err: nil}, // status check
				{Name: "message", Result: "", Err: nil}, // make reservation
				{Name: "message", Result: "", Err: nil}, // release reservation
			},
			thinLsOutput:  usage,
			expectedError: false,
			deviceID:      "2",
			expectedUsage: 23456,
		},
		{
			name: "existing reservation - ok",
			dmsetupCommands: []fake.DmsetupCommand{
				// status check
				{Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 39 rw no_discard_passdown error_if_no_space - ", Err: nil},
				// release reservation
				{Name: "message", Result: "", Err: nil},
				// make reservation
				{Name: "message", Result: "", Err: nil},
				// release reservation
				{Name: "message", Result: "", Err: nil},
			},
			thinLsOutput:  usage,
			expectedError: false,
			deviceID:      "3",
			expectedUsage: 34567,
		},
		{
			name: "failure releasing existing reservation",
			dmsetupCommands: []fake.DmsetupCommand{
				// status check
				{Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 39 rw no_discard_passdown error_if_no_space - ", Err: nil},
				// release reservation
				{Name: "message", Result: "", Err: fmt.Errorf("not gonna work")},
			},
			expectedError: true,
		},
		{
			name: "failure making reservation",
			dmsetupCommands: []fake.DmsetupCommand{
				// status check
				{Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 39 rw no_discard_passdown error_if_no_space - ", Err: nil},
				// release reservation
				{Name: "message", Result: "", Err: nil},
				// make reservation
				{Name: "message", Result: "", Err: fmt.Errorf("not gonna work")},
			},
			expectedError: true,
		},
		{
			name: "failure running thin_ls",
			dmsetupCommands: []fake.DmsetupCommand{
				// status check
				{Name: "status", Result: "0 75497472 thin-pool 65 327/524288 14092/589824 39 rw no_discard_passdown error_if_no_space - ", Err: nil},
				// release reservation
				{Name: "message", Result: "", Err: nil},
				// make reservation
				{Name: "message", Result: "", Err: nil},
				// release reservation
				{Name: "message", Result: "", Err: nil},
			},
			thinLsErr:     fmt.Errorf("not gonna work"),
			expectedError: true,
		},
	}

	for _, tc := range cases {
		dmsetup := fake.NewFakeDmsetupClient(t, tc.dmsetupCommands...)
		thinLsClient := fake.NewFakeThinLsClient(tc.thinLsOutput, tc.thinLsErr)
		watcher := &ThinPoolWatcher{
			poolName:       "test pool name",
			metadataDevice: "/dev/mapper/metadata-device",
			lock:           &sync.RWMutex{},
			period:         15 * time.Second,
			stopChan:       make(chan struct{}),
			dmsetup:        dmsetup,
			thinLsClient:   thinLsClient,
		}

		err := watcher.Refresh()
		if err != nil {
			if !tc.expectedError {
				t.Errorf("%v: unexpected error: %v", tc.name, err)
			}
			continue
		} else if tc.expectedError {
			t.Errorf("%v: unexpected success", tc.name)
			continue
		}

		actualUsage, err := watcher.GetUsage(tc.deviceID)
		if err != nil {
			t.Errorf("%v: device ID not found: %v", tc.deviceID, err)
			continue
		}

		if e, a := tc.expectedUsage, actualUsage; e != a {
			t.Errorf("%v: actual usage did not match expected usage: expected: %v got: %v", tc.name, e, a)
		}
	}
}

func TestCheckReservation(t *testing.T) {
	cases := []struct {
		name           string
		statusResult   string
		statusErr      error
		expectedResult bool
		expectedErr    error
	}{
		{
			name:           "existing reservation 1",
			statusResult:   "0 75497472 thin-pool 65 327/524288 14092/589824 36 rw no_discard_passdown queue_if_no_space - ",
			expectedResult: true,
		},
		{
			name:           "existing reservation 2",
			statusResult:   "0 12345 thin-pool 65 327/45678 14092/45678 36 rw discard_passdown error_if_no_space needs_check ",
			expectedResult: true,
		},
		{
			name:           "no reservation 1",
			statusResult:   "0 75497472 thin-pool 65 327/524288 14092/589824 - rw no_discard_passdown error_if_no_space - ",
			expectedResult: false,
		},
		{
			name:           "no reservation 2",
			statusResult:   "0 75 thin-pool 65 327/12345 14092/589824 - rw no_discard_passdown queue_if_no_space - ",
			expectedResult: false,
		},
		{
			name:           "no reservation 2",
			statusResult:   "0 75 thin-pool 65 327/12345 14092/589824 - rw no_discard_passdown queue_if_no_space - ",
			expectedResult: false,
		},
	}

	for _, tc := range cases {
		fakeDmsetupClient := fake.NewFakeDmsetupClient(t)
		fakeDmsetupClient.AddCommand("status", tc.statusResult, tc.statusErr)
		watcher := &ThinPoolWatcher{dmsetup: fakeDmsetupClient}
		actualResult, err := watcher.checkReservation("test pool")
		if err != nil {
			if tc.expectedErr == nil {
				t.Errorf("%v: unexpected error running checkReservation: %v", tc.name, err)
			}
		} else if tc.expectedErr != nil {
			t.Errorf("%v: unexpected success running checkReservation", tc.name)
		}

		if e, a := tc.expectedResult, actualResult; e != a {
			t.Errorf("%v: unexpected result from checkReservation: expected: %v got: %v", tc.name, e, a)
		}
	}
}
