Skip to content

Commit 4031035

Browse files
author
Elias Nygren
committed
add firewall functionality
1 parent ffc4a99 commit 4031035

File tree

9 files changed

+361
-41
lines changed

9 files changed

+361
-41
lines changed

test/conftest.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@ class Mock():
1515

1616
@staticmethod
1717
def read_from_file(filename):
18-
18+
1919
filename = filename.replace("/", "_")
2020

2121
cwd = os.path.dirname(__file__)
2222
f = open(cwd + '/json_data/'+filename, 'r')
2323
return f.read()
2424

2525
@staticmethod
26-
def mock_get(target):
27-
data = Mock.read_from_file(target + '.json')
26+
def mock_get(target, response_file=None):
27+
if not response_file:
28+
response_file = target + '.json'
29+
30+
data = Mock.read_from_file(response_file)
2831
responses.add(responses.GET, Mock.base_url + '/' + target,
2932
body=data,
3033
status=200,
@@ -35,11 +38,10 @@ def mock_get(target):
3538
def __put_post_callback(request, target, data):
3639
data_field = target.split("/")[0]
3740
payload = json.loads(request.body)
38-
41+
3942
for field in data[data_field]:
4043
if field in payload[data_field]:
4144
data[data_field][field] = payload[data_field][field]
42-
print(data)
4345
return(200, {}, json.dumps(data))
4446

4547
@staticmethod
@@ -76,7 +78,7 @@ def mock_server_operation(target):
7678
targetfile = "/".join( targetsplit[:2] )
7779

7880
data = json.loads( Mock.read_from_file(targetfile + '.json') )
79-
81+
8082
# API will always respond state: "started", see: Server.stop, Server.start, Server,restart
8183
data["server"]["state"] = "started"
8284

test/json_data/firewall_rule.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"firewall_rule" : {
3+
"protocol" : "tcp",
4+
"destination_port_end" : "22",
5+
"source_address_start" : "192.168.1.0",
6+
"source_address_end" : "192.168.1.255",
7+
"position" : "1",
8+
"source_port_end" : "",
9+
"source_port_start" : "",
10+
"destination_address_start" : "",
11+
"direction" : "in",
12+
"action" : "accept",
13+
"icmp_type" : "",
14+
"destination_port_start" : "22",
15+
"destination_address_end" : ""
16+
}
17+
}

test/json_data/firewall_rules.json

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{
2+
"firewall_rules": {
3+
"firewall_rule" : [
4+
{
5+
"action" : "accept",
6+
"destination_address_end" : "",
7+
"destination_address_start" : "",
8+
"destination_port_end" : "80",
9+
"destination_port_start" : "80",
10+
"direction" : "in",
11+
"family" : "IPv4",
12+
"icmp_type" : "",
13+
"position" : "1",
14+
"protocol" : "",
15+
"source_address_end" : "",
16+
"source_address_start" : "",
17+
"source_port_end" : "",
18+
"source_port_start" : ""
19+
},
20+
{
21+
"action" : "accept",
22+
"destination_address_end" : "",
23+
"destination_address_start" : "",
24+
"destination_port_end" : "22",
25+
"destination_port_start" : "22",
26+
"direction" : "in",
27+
"family" : "IPv4",
28+
"icmp_type" : "",
29+
"position" : "2",
30+
"protocol" : "tcp",
31+
"source_address_end" : "192.168.1.255",
32+
"source_address_start" : "192.168.1.1",
33+
"source_port_end" : "",
34+
"source_port_start" : ""
35+
},
36+
{
37+
"action" : "accept",
38+
"destination_address_end" : "",
39+
"destination_address_start" : "",
40+
"destination_port_end" : "22",
41+
"destination_port_start" : "22",
42+
"direction" : "in",
43+
"family" : "IPv6",
44+
"icmp_type" : "",
45+
"position" : "3",
46+
"protocol" : "tcp",
47+
"source_address_end" : "2a04:3540:1000:aaaa:bbbb:cccc:d001",
48+
"source_address_start" : "2a04:3540:1000:aaaa:bbbb:cccc:d001",
49+
"source_port_end" : "",
50+
"source_port_start" : ""
51+
},
52+
{
53+
"action" : "accept",
54+
"destination_address_end" : "",
55+
"destination_address_start" : "",
56+
"destination_port_end" : "",
57+
"destination_port_start" : "",
58+
"direction" : "in",
59+
"family" : "IPv4",
60+
"icmp_type" : "8",
61+
"position" : "4",
62+
"protocol" : "icmp",
63+
"source_address_end" : "",
64+
"source_address_start" : "",
65+
"source_port_end" : "",
66+
"source_port_start" : ""
67+
},
68+
{
69+
"action" : "drop",
70+
"destination_address_end" : "",
71+
"destination_address_start" : "",
72+
"destination_port_end" : "",
73+
"destination_port_start" : "",
74+
"direction" : "in",
75+
"family" : "",
76+
"icmp_type" : "",
77+
"position" : "5",
78+
"protocol" : "",
79+
"source_address_end" : "",
80+
"source_address_start" : "",
81+
"source_port_end" : "",
82+
"source_port_start" : ""
83+
}
84+
]
85+
}
86+
}

test/test_server.py

Lines changed: 105 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import json
33
from conftest import Mock
44
import pytest
5+
from upcloud import FirewallRule
56

67
class TestServer():
78
@responses.activate
@@ -36,7 +37,7 @@ def test_start_server(self, manager):
3637
server = manager.get_server("009d64ef-31d1-4684-a26b-c86c955cbf46")
3738

3839
assert server.state == "stopped"
39-
40+
4041
data = Mock.mock_server_operation("server/009d64ef-31d1-4684-a26b-c86c955cbf46/start")
4142
server.start()
4243

@@ -48,7 +49,7 @@ def test_stop_server(self, manager):
4849
server = manager.get_server("00798b85-efdc-41ca-8021-f6ef457b8531")
4950

5051
assert server.state == "started"
51-
52+
5253
data = Mock.mock_server_operation("server/00798b85-efdc-41ca-8021-f6ef457b8531/stop")
5354
server.stop()
5455

@@ -60,7 +61,7 @@ def test_restart_server(self, manager):
6061
server = manager.get_server("00798b85-efdc-41ca-8021-f6ef457b8531")
6162

6263
assert server.state == "started"
63-
64+
6465
data = Mock.mock_server_operation("server/00798b85-efdc-41ca-8021-f6ef457b8531/restart")
6566
server.restart()
6667

@@ -71,17 +72,17 @@ def test_attach_and_detach_IP(self, manager):
7172
data = Mock.mock_get("server/00798b85-efdc-41ca-8021-f6ef457b8531")
7273
server = manager.get_server("00798b85-efdc-41ca-8021-f6ef457b8531")
7374
assert len(server.ip_addresses) == 2
74-
75+
7576
data = Mock.mock_post("ip_address")
7677
server.add_IP()
7778
assert len(server.ip_addresses) == 3
78-
79+
7980
Mock.mock_delete("ip_address/"+server.ip_addresses[2].address)
8081
server.remove_IP(server.ip_addresses[2])
8182
assert len(server.ip_addresses) == 2
8283

8384
@responses.activate
84-
def test_attach_and_detach_storage(self, manager):
85+
def test_attach_and_detach_storage(self, manager):
8586
data = Mock.mock_get("server/00798b85-efdc-41ca-8021-f6ef457b8531")
8687
server = manager.get_server("00798b85-efdc-41ca-8021-f6ef457b8531")
8788
assert len(server.storage_devices) == 1
@@ -90,9 +91,9 @@ def test_attach_and_detach_storage(self, manager):
9091
data = Mock.mock_get("storage/01d4fcd4-e446-433b-8a9c-551a1284952e")
9192
storage = manager.get_storage("01d4fcd4-e446-433b-8a9c-551a1284952e")
9293

93-
94+
9495
responses.add(
95-
responses.POST,
96+
responses.POST,
9697
Mock.base_url + "/server/00798b85-efdc-41ca-8021-f6ef457b8531/storage/attach",
9798
body = Mock.read_from_file("storage_attach.json"),
9899
status = 200,
@@ -104,7 +105,7 @@ def test_attach_and_detach_storage(self, manager):
104105
assert server.storage_devices[1].title == "Operating system disk"
105106

106107
responses.add(
107-
responses.POST,
108+
responses.POST,
108109
Mock.base_url + "/server/00798b85-efdc-41ca-8021-f6ef457b8531/storage/detach",
109110
body = Mock.read_from_file("storage_attach.json"),
110111
status = 200,
@@ -136,8 +137,101 @@ def test_update_server_oop(self, manager):
136137
def test_update_server_non_updateable_fields(self, manager):
137138
data = Mock.mock_get("server/00798b85-efdc-41ca-8021-f6ef457b8531")
138139
server = manager.get_server("00798b85-efdc-41ca-8021-f6ef457b8531")
139-
140+
140141
with pytest.raises(Exception) as excinfo:
141-
server.state = "rekt"
142+
server.state = "rekt"
142143
assert "'state' is a readonly field" in str(excinfo.value)
143144

145+
@responses.activate
146+
def test_add_firewall_rule(self, manager):
147+
Mock.mock_get("server/00798b85-efdc-41ca-8021-f6ef457b8531")
148+
server = manager.get_server("00798b85-efdc-41ca-8021-f6ef457b8531")
149+
150+
def callback(request):
151+
required_fields = [
152+
'position', 'direction', 'family', 'protocol',
153+
'source_address_start', 'source_address_end',
154+
'destination_port_start', 'destination_port_end',
155+
'action'
156+
]
157+
158+
request_body = json.loads(request.body)
159+
160+
for field in required_fields:
161+
if field not in request_body:
162+
raise Exception('missing required field: {0}'.format(field))
163+
164+
return(201, {}, json.dumps({ 'firewall_rule': request_body }))
165+
166+
167+
responses.add_callback(
168+
responses.POST,
169+
Mock.base_url + "/server/00798b85-efdc-41ca-8021-f6ef457b8531/firewall_rule",
170+
content_type='application/json',
171+
callback=callback
172+
)
173+
174+
returned_firewall = server.add_firewall_rule(FirewallRule(
175+
position = "1",
176+
direction = "in",
177+
family = "IPv4",
178+
protocol = "tcp",
179+
source_address_start = "192.168.1.1",
180+
source_address_end = "192.168.1.255",
181+
destination_port_start = "22",
182+
destination_port_end = "22",
183+
action = "accept"
184+
))
185+
186+
# everything should run without errors, returned created object
187+
assert returned_firewall.position == "1"
188+
assert returned_firewall.direction == "in"
189+
assert returned_firewall.source_address_end == "192.168.1.255"
190+
191+
192+
@responses.activate
193+
def test_remove_firewall_rule(self, manager):
194+
Mock.mock_get("server/00798b85-efdc-41ca-8021-f6ef457b8531")
195+
server = manager.get_server("00798b85-efdc-41ca-8021-f6ef457b8531")
196+
197+
target = "server/00798b85-efdc-41ca-8021-f6ef457b8531/firewall_rule"
198+
Mock.mock_get(target, "firewall_rules.json")
199+
firewall_rules = server.get_firewall_rules()
200+
201+
Mock.mock_delete("server/00798b85-efdc-41ca-8021-f6ef457b8531/firewall_rule/1")
202+
res = firewall_rules[0].destroy()
203+
204+
Mock.mock_delete("server/00798b85-efdc-41ca-8021-f6ef457b8531/firewall_rule/1")
205+
res = server.remove_firewall_rule(firewall_rules[0])
206+
207+
assert res == {}
208+
209+
@responses.activate
210+
def test_list_and_get_firewall_rules(self, manager):
211+
Mock.mock_get("server/00798b85-efdc-41ca-8021-f6ef457b8531")
212+
server = manager.get_server("00798b85-efdc-41ca-8021-f6ef457b8531")
213+
214+
target = "server/00798b85-efdc-41ca-8021-f6ef457b8531/firewall_rule"
215+
Mock.mock_get(target, "firewall_rules.json")
216+
firewall_rules = server.get_firewall_rules()
217+
218+
assert firewall_rules[0].position == "1"
219+
220+
221+
222+
223+
224+
225+
226+
227+
228+
229+
230+
231+
232+
233+
234+
235+
236+
237+

upcloud/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .storage import Storage
1414
from .ip_address import IP_address
1515
from .firewall import Firewall
16+
from .firewall import FirewallRule
1617
from .cloud_manager import CloudManager
1718
from .tools import OperatingSystems
1819
from .tools import ZONE

upcloud/cloud_manager/cloud_manager.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
from .server_mixin import ServerManager
55
from .ip_address_mixin import IPManager
66
from .storage_mixin import StorageManager
7+
from .firewall_mixin import FirewallManager
78

89
import base64
910

1011

11-
class CloudManager(BaseAPI, ServerManager, IPManager, StorageManager):
12+
class CloudManager(BaseAPI, ServerManager, IPManager, StorageManager, FirewallManager):
1213
"""
13-
CloudManager contains the core functionality of the upcloud API library.
14+
CloudManager contains the core functionality of the upcloud API library.
1415
All other managers are mixed in so code can be organized in corresponding submanager classes.
1516
"""
1617

@@ -21,8 +22,8 @@ def authenticate(self):
2122
return self.get_account()
2223

2324
def get_account(self):
24-
return self.get_request("/account")
25-
25+
return self.get_request("/account")
26+
2627
def get_zones(self):
2728
return self.get_request("/zone")
2829

@@ -34,4 +35,4 @@ def get_prices(self):
3435

3536
def get_server_sizes(self):
3637
return self.get_request("/server_size")
37-
38+

0 commit comments

Comments
 (0)