vtest "Test std.rollback"

# ws_emu triggers #3550
feature !workspace_emulator

# bug regressions:
# - #3009
# - #3083
# - #3329
# - #3385

server s1 {
	rxreq
	expect req.url == "/foo"
	expect req.http.foobar == "bar"
	txresp -status 400
	accept
	rxreq
	expect req.url == "/bar"
	expect req.http.foobar == "foo"
	txresp
	accept
	rxreq
	expect req.url == "/baz"
	expect req.http.foobar == "qux"
	txresp -status 400
	accept
	rxreq
	expect req.url == "/qux"
	expect req.http.foobar == "baz"
	txresp
} -start

vinyl v1 -vcl+backend {
	import std;

	sub vcl_recv {
		if (req.url == "/foo") {
			set req.http.foobar = "bar";
		}
	}

	sub vcl_deliver {
		if (resp.status == 400) {
			std.rollback(req);
			set req.url = "/bar";
			return (restart);
		}
	}
} -start

client c1 {
	txreq -url "/foo" -hdr "foobar: foo"
	rxresp
} -run

vinyl v1 -vcl+backend {
	import std;

	sub vcl_backend_fetch {
		if (bereq.url == "/baz") {
			set bereq.http.foobar = "qux";
		}
	}

	sub vcl_backend_response {
		if (beresp.status == 400) {
			std.rollback(bereq);
			set bereq.url = "/qux";
			return (retry);
		}
	}
}

client c1 {
	txreq -url "/baz" -hdr "foobar: baz"
	rxresp
} -run

server s1 -wait

vinyl v1 -cliok "param.set workspace_client 12000"
vinyl v1 -cliok "param.set workspace_backend 12000"

# Dont panic
vinyl v1 -vcl+backend {
	import std;
	import vtc;

	sub vcl_recv {
		set req.http.test = "1";
	}

	sub vcl_backend_fetch {
		unset bereq.http.test;
	}

	sub vcl_backend_response {
		std.rollback(bereq);
		set beresp.http.test = bereq.http.test;
		vtc.workspace_alloc(backend, -10);
	}
}

server s1 {
	rxreq
	expect req.url == "/1"
	txresp
} -start

client c1 {
	txreq -url /1
	rxresp
	expect resp.status == 503
	expect resp.http.test == <undef>
} -run

server s1 -wait

# Dont run out of workspace
vinyl v1 -vcl+backend {
	import std;
	import vtc;

	sub vcl_backend_fetch {
		vtc.workspace_alloc(backend, 1000);
	}

	sub vcl_backend_response {
		if (bereq.retries == 0) {
			vtc.workspace_alloc(backend, -10);
			std.rollback(bereq);
			return (retry);
		}
	}
}

server s1 -repeat 2 -keepalive {
	rxreq
	expect req.url == "/2"
	txresp
} -start

client c2 {
	txreq -url /2
	rxresp
	expect resp.status == 200
} -run

server s1 -wait

# Keep workspace intact (and possibly overflow)
vinyl v1 -vcl+backend {
	import std;
	import vtc;

	sub vcl_backend_fetch {
		set bereq.http.fetch = "Fetch value " + bereq.retries;
	}

	sub vcl_backend_response {
		if (bereq.retries == 0) {
			std.rollback(bereq);
			set bereq.http.response = "123";
			set bereq.http.response2 = "Another response";
			if (bereq.url == "/4") {
				vtc.workspace_alloc(backend, -10);
			} else if (bereq.url == "/5") {
				vtc.workspace_alloc(backend, -10);
				std.rollback(bereq);
			}
			return (retry);
		}
		set beresp.http.fetch = bereq.http.fetch;
		set beresp.http.response = bereq.http.response;
		set beresp.http.response2 = bereq.http.response2;
	}
}

server s1 -repeat 5 -keepalive {
	rxreq
	txresp
} -start

client c3 {
	txreq -url /3
	rxresp
	expect resp.status == 200
	expect resp.http.fetch == "Fetch value 1"
	expect resp.http.response == "123"
	expect resp.http.response2 == "Another response"

	txreq -url /4
	rxresp
	expect resp.status == 503

	txreq -url /5
	rxresp
	expect resp.status == 200
	expect resp.http.fetch == "Fetch value 1"
	expect resp.http.response == ""
	expect resp.http.response2 == ""
} -run

server s1 -wait

# CLIENT

# Dont panic
vinyl v1 -vcl+backend {
	import std;
	import debug;
	import vtc;

	sub vcl_recv {
		unset req.http.test;
	}

	sub vcl_deliver {
		if (req.url ~ "/veto") {
			debug.fail_task_fini();
		}
		if (req.url ~ "/ok") {
			debug.ok_task_fini();
		}
		std.rollback(req);
		set resp.http.test = req.http.test;
		vtc.workspace_alloc(client, -200);
	}
}

# XXX server keeps params, so we need to reset previous -repeat 5
server s1 -repeat 3 -keepalive {
	rxreq
	expect req.url ~ "^/6"
	txresp
} -start

client c4 {
	txreq -url /6 -hdr "test: 1"
	rxresp
	expect resp.status == 200
	expect resp.http.test == "1"
	txreq -url /6/veto/ok -hdr "test: 1"
	rxresp
	expect resp.status == 200
	expect resp.http.test == "1"
	txreq -url /6/veto -hdr "test: 1"
	rxresp
	expect resp.status == 503
} -run

server s1 -wait

# Dont run out of workspace
vinyl v1 -vcl+backend {
	import std;
	import vtc;

	sub vcl_recv {
		vtc.workspace_alloc(client, 1000);
	}

	sub vcl_deliver {
		if (req.restarts == 0) {
			vtc.workspace_alloc(client, -10);
			std.rollback(req);
			return (restart);
		}
	}
}

server s1 -repeat 1 {
	rxreq
	expect req.url == "/7"
	txresp
} -start

client c5 {
	txreq -url /7
	rxresp
	expect resp.status == 200
} -run

server s1 -wait

# Keep workspace intact (and possibly overflow)
vinyl v1 -vcl+backend {
	import std;
	import vtc;

	sub vcl_recv {
		set req.http.fetch = "Fetch value " + req.restarts;
	}

	sub vcl_deliver {
		if (req.restarts == 0) {
			std.rollback(req);
			set req.http.response = "123";
			set req.http.response2 = "Another response";
			if (req.url == "/9") {
				vtc.workspace_alloc(client, -200);
			} else if (req.url == "/10") {
				vtc.workspace_alloc(client, -10);
				std.rollback(req);
			}
			return (restart);
		}
		set resp.http.fetch = req.http.fetch;
		set resp.http.response = req.http.response;
		set resp.http.response2 = req.http.response2;
	}
}

server s1 -repeat 3 -keepalive {
	rxreq
	txresp
} -start

client c6 {
	txreq -url /8 -hdr "Host: 127.0.0.1"
	rxresp
	expect resp.status == 200
	expect resp.http.fetch == "Fetch value 1"
	expect resp.http.response == "123"
	expect resp.http.response2 == "Another response"

	txreq -url /9 -hdr "Host: 127.0.0.1"
	rxresp
	expect resp.status == 200
	expect resp.http.fetch == "Fetch value 1"
	expect resp.http.response == "123"
	expect resp.http.response2 == "Another response"

	txreq -url /10 -hdr "Host: 127.0.0.1"
	rxresp
	expect resp.status == 200
	expect resp.http.fetch == "Fetch value 1"
	expect resp.http.response == ""
} -run

server s1 -wait

# this could work, but would need additional coding to save
# the right snapshot of the bereq on the req ws
vinyl v1 -errvcl {Not available in subroutine 'vcl_pipe'} {
	import std;

	backend proforma None;

	sub vcl_pipe {
		std.rollback(bereq);
	}
}

# vcl_pipe { std.rollback(req); } cannot work unless it also implied
# rolling back the bereq first.
# We would want to remove req from vcl_pipe, but that could break
# vmods, so we fail specifically at runtime

vinyl v1 -errvcl {Not available in subroutine 'vcl_pipe'} {
	import std;

	backend proforma None;

	sub vcl_recv {
		return (pipe);
	}
	sub vcl_pipe {
		std.rollback(req);
	}
}

