def reboot(self, hard: bool = False) -> None: question = f"are you sure you want to reboot {self.full_name}?" if self.state == self.UP and not self.depl.logger.confirm(question): return if hard: self.logger.log_start(f"sending hard reset to {self.full_name}...") self.wait_on_action(self.get_client().servers.reset( Server(self.vm_id))) else: self.logger.log_start( f"sending ACPI reboot request to {self.full_name}...") self.wait_on_action(self.get_client().servers.reboot( Server(self.vm_id))) self.wait_for_ssh() self.state = self.UP
def test_created_is_datetime(self): server = Server(id=1, created="2016-01-30T23:50+00:00") assert server.created == datetime.datetime(2016, 1, 30, 23, 50, tzinfo=tzoffset(None, 0))
def _handle_changed_floating_ips(self, defn: HetznerCloudDefinition, allow_recreate: bool) -> None: """ Detects and corrects any floating IP state desynchronisation. """ assigned: Set[str] = { x.name for x in self.get_instance().public_net.floating_ips } for name in self.ip_addresses.keys(): fip: Optional[BoundFloatingIP] = self.get_client( ).floating_ips.get_by_name(name) # Detect manually destroyed floating IPs if fip is None: if name not in defn.ip_addresses: # we dont need it self.logger.warn( f"forgetting about floating IP ‘{name}’ that no longer" " exists and is no longer needed by the deployment" " specification") self._update_attr("ip_addresses", name, None) else: if name.startswith("nixops-" + self.depl.uuid): raise Exception( f"floating IP ‘{name}’ (used by {self.full_name})" " no longer exists; run ‘nixops deploy --check’" " to update resource state") else: raise Exception( f"floating IP ‘{name}’ (used by {self.full_name})" " was manually destroyed") # Detect unassigned floating IPs elif name not in assigned: if name not in defn.ip_addresses: # we dont need it self.logger.warn( f"forgetting about unassigned floating IP ‘{name}’ [{fip.id}]" " that is no longer needed by the deployment specification" ) else: # we do need it self.logger.warn( f"floating IP ‘{name}’ [{fip.id}] was manually unassigned;" " will reassign it.") self._update_attr("ip_addresses", name, None) # Assign missing floating IPs. for name in defn.ip_addresses: if name not in self.ip_addresses: fip = self.get_client().floating_ips.get_by_name(name) if fip is None: raise Exception(f"tried to assign floating IP ‘{name}’" " but it doesn't exist...") self.logger.log( f"assigning floating IP ‘{name}’ [{fip.id}]...") self.wait_on_action(fip.assign(Server(self.vm_id))) self._update_attr("ip_addresses", name, fip.ip)
def stop(self) -> None: question = f"are you sure you want to stop {self.full_name}?" if not self.depl.logger.confirm(question): return self.logger.log_start( f"sending ACPI shutdown request to {self.full_name}...") self.wait_on_action(self.get_client().servers.shutdown( Server(self.vm_id))) while not self._check_status("off"): time.sleep(1) self.state = self.STOPPED
def delete(): with open(data_file, "r") as file: data = json.load(file) client = Client(token=data["token"]) response = client.servers.delete(server=Server(id=data["server_id"]), ) data["server_id"] = "SERVER-IS-DOWN" with open(data_file, "w") as file: json.dump(data, file, indent=4) click.echo("deletion complete")
def destroy(self, wipe=False): if self.vm_id is None: return True if wipe: self.warn("Wipe is not supported") if not self.depl.logger.confirm( f"are you sure you want to destroy Hetzner server {self.name}?" ): return False self.log_start("destroying Hetzner Cloud VM...") self._client.servers.delete(Server(id=self.vm_id)) self.log_end("") self._reset() return True
def test_create(self, firewalls_client, response_create_firewall): firewalls_client._client.request.return_value = response_create_firewall response = firewalls_client.create( "Corporate Intranet Protection", rules=[ FirewallRule(direction=FirewallRule.DIRECTION_IN, protocol=FirewallRule.PROTOCOL_ICMP, source_ips=["0.0.0.0/0"]) ], resources=[ FirewallResource(type=FirewallResource.TYPE_SERVER, server=Server(id=4711)), FirewallResource(type=FirewallResource.TYPE_LABEL_SELECTOR, label_selector=FirewallResourceLabelSelector( selector="key==value")) ]) firewalls_client._client.request.assert_called_with( url="/firewalls", method="POST", json={ 'name': "Corporate Intranet Protection", 'rules': [{ "direction": "in", "protocol": "icmp", "source_ips": ["0.0.0.0/0"] }], "apply_to": [{ "type": "server", "server": { "id": 4711 } }, { "type": "label_selector", "label_selector": { "selector": "key==value" } }], }) bound_firewall = response.firewall actions = response.actions assert bound_firewall._client is firewalls_client assert bound_firewall.id == 38 assert bound_firewall.name == "Corporate Intranet Protection" assert len(bound_firewall.applied_to) == 2 assert len(actions) == 2
def attach_to_network() -> bool: try: self.wait_on_action( self.get_client().servers.attach_to_network( server=Server(self.vm_id), network=nw, ip=x["privateIpAddress"], alias_ips=x["aliasIpAddresses"], )) except APIException as e: if e.code == "conflict": return False else: raise else: self._update_attr("server_networks", x["network"], x) return True
class TestBoundFloatingIPs(object): @pytest.fixture() def bound_floating_ip(self, hetzner_client): return BoundFloatingIP(client=hetzner_client.floating_ips, data=dict(id=4711)) def test_get_actions(self, bound_floating_ip): actions = bound_floating_ip.get_actions() assert len(actions) == 1 assert actions[0].id == 13 assert actions[0].command == "assign_floating_ip" def test_update(self, bound_floating_ip): floating_ip = bound_floating_ip.update(description="New description", labels={}, name="Web Frontend") assert floating_ip.id == 4711 assert floating_ip.description == "New description" assert floating_ip.name == "Web Frontend" def test_delete(self, bound_floating_ip): delete_success = bound_floating_ip.delete() assert delete_success is True @pytest.mark.parametrize( "server", (Server(id=1), BoundServer(mock.MagicMock(), dict(id=1)))) def test_assign(self, bound_floating_ip, server): action = bound_floating_ip.assign(server) assert action.id == 13 assert action.progress == 0 assert action.command == "assign_floating_ip" def test_unassign(self, bound_floating_ip): action = bound_floating_ip.unassign() assert action.id == 13 assert action.progress == 0 assert action.command == "unassign_floating_ip" def test_change_dns_ptr(self, bound_floating_ip): action = bound_floating_ip.change_dns_ptr("1.2.3.4", "server02.example.com") assert action.id == 13 assert action.progress == 0 assert action.command == "change_dns_ptr"
def test_remove_target(self, hetzner_client, response_remove_target, bound_load_balancer): hetzner_client.request.return_value = response_remove_target target = LoadBalancerTarget(server=Server(id=100)) action = bound_load_balancer.remove_target(target) hetzner_client.request.assert_called_with( json={ 'type': None, 'server': { "id": 100 } }, url="/load_balancers/14/actions/remove_target", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "remove_target"
def test_add_target(self, hetzner_client, response_add_target, bound_load_balancer): hetzner_client.request.return_value = response_add_target target = LoadBalancerTarget(server=Server(id=1), use_private_ip=True) action = bound_load_balancer.add_target(target) hetzner_client.request.assert_called_with( json={ 'type': None, 'server': { "id": 1 }, 'use_private_ip': True }, url="/load_balancers/14/actions/add_target", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "add_target"
def test_remove_from_resources(self, hetzner_client, bound_firewall, response_set_rules): hetzner_client.request.return_value = response_set_rules actions = bound_firewall.remove_from_resources([ FirewallResource(type=FirewallResource.TYPE_SERVER, server=Server(id=5)) ]) hetzner_client.request.assert_called_with( url="/firewalls/1/actions/remove_from_resources", method="POST", json={"remove_from": [{ "type": "server", "server": { "id": 5 } }]}) assert actions[0].id == 13 assert actions[0].progress == 100
def test_apply_to_resources(self, firewalls_client, firewall, response_set_rules): firewalls_client._client.request.return_value = response_set_rules actions = firewalls_client.apply_to_resources(firewall, [ FirewallResource(type=FirewallResource.TYPE_SERVER, server=Server(id=5)) ]) firewalls_client._client.request.assert_called_with( url="/firewalls/1/actions/apply_to_resources", method="POST", json={"apply_to": [{ "type": "server", "server": { "id": 5 } }]}) assert actions[0].id == 13 assert actions[0].progress == 100
class TestBoundVolume(object): @pytest.fixture() def bound_volume(self, hetzner_client): return BoundVolume(client=hetzner_client.volumes, data=dict(id=4711)) def test_get_actions(self, bound_volume): actions = bound_volume.get_actions() assert len(actions) == 1 assert actions[0].id == 13 assert actions[0].command == "attach_volume" def test_update(self, bound_volume): volume = bound_volume.update(name="new-name", labels={}) assert volume.id == 4711 assert volume.name == "new-name" def test_delete(self, bound_volume): delete_success = bound_volume.delete() assert delete_success is True @pytest.mark.parametrize( "server", (Server(id=1), BoundServer(mock.MagicMock(), dict(id=1)))) def test_attach(self, hetzner_client, bound_volume, server): action = bound_volume.attach(server) assert action.id == 13 assert action.progress == 0 assert action.command == "attach_volume" def test_detach(self, hetzner_client, bound_volume): action = bound_volume.detach() assert action.id == 13 assert action.progress == 0 assert action.command == "detach_volume" def test_resize(self, hetzner_client, bound_volume): action = bound_volume.resize(50) assert action.id == 13 assert action.progress == 0 assert action.command == "resize_volume"
class TestBoundLoadBalancer(object): @pytest.fixture() def bound_load_balancer(self, hetzner_client): return BoundLoadBalancer(client=hetzner_client.load_balancers, data=dict(id=14)) def test_bound_load_balancer_init(self, response_load_balancer): bound_load_balancer = BoundLoadBalancer( client=mock.MagicMock(), data=response_load_balancer['load_balancer']) assert bound_load_balancer.id == 4711 assert bound_load_balancer.name == 'Web Frontend' @pytest.mark.parametrize("params", [{"page": 1, "per_page": 10}, {}]) def test_get_actions_list(self, hetzner_client, bound_load_balancer, response_get_actions, params): hetzner_client.request.return_value = response_get_actions result = bound_load_balancer.get_actions_list(**params) hetzner_client.request.assert_called_with( url="/load_balancers/14/actions", method="GET", params=params) actions = result.actions assert result.meta is None assert len(actions) == 1 assert isinstance(actions[0], BoundAction) assert actions[0].id == 13 assert actions[0].command == "change_protection" @pytest.mark.parametrize("params", [{}]) def test_get_actions(self, hetzner_client, bound_load_balancer, response_get_actions, params): hetzner_client.request.return_value = response_get_actions actions = bound_load_balancer.get_actions(**params) params.update({'page': 1, 'per_page': 50}) hetzner_client.request.assert_called_with( url="/load_balancers/14/actions", method="GET", params=params) assert len(actions) == 1 assert isinstance(actions[0], BoundAction) assert actions[0].id == 13 assert actions[0].command == "change_protection" def test_update(self, hetzner_client, bound_load_balancer, response_update_load_balancer): hetzner_client.request.return_value = response_update_load_balancer load_balancer = bound_load_balancer.update(name="new-name", labels={}) hetzner_client.request.assert_called_with(url="/load_balancers/14", method="PUT", json={ "name": "new-name", "labels": {} }) assert load_balancer.id == 4711 assert load_balancer.name == "new-name" def test_delete(self, hetzner_client, generic_action, bound_load_balancer): hetzner_client.request.return_value = generic_action delete_success = bound_load_balancer.delete() hetzner_client.request.assert_called_with(url="/load_balancers/14", method="DELETE") assert delete_success is True def test_add_service(self, hetzner_client, response_add_service, bound_load_balancer): hetzner_client.request.return_value = response_add_service service = LoadBalancerService(listen_port=80, protocol="http") action = bound_load_balancer.add_service(service) hetzner_client.request.assert_called_with( json={ 'protocol': 'http', 'listen_port': 80 }, url="/load_balancers/14/actions/add_service", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "add_service" def test_delete_service(self, hetzner_client, response_delete_service, bound_load_balancer): hetzner_client.request.return_value = response_delete_service service = LoadBalancerService(listen_port=12) action = bound_load_balancer.delete_service(service) hetzner_client.request.assert_called_with( json={'listen_port': 12}, url="/load_balancers/14/actions/delete_service", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "delete_service" @pytest.mark.parametrize( "target,params", [(LoadBalancerTarget( type="server", server=Server(id=1), use_private_ip=True), { 'server': { "id": 1 } }), (LoadBalancerTarget(type="ip", ip=LoadBalancerTargetIP(ip="127.0.0.1")), { 'ip': { "ip": "127.0.0.1" } }), (LoadBalancerTarget(type="label_selector", label_selector=LoadBalancerTargetLabelSelector( selector="abc=def")), { 'label_selector': { "selector": "abc=def" } })]) def test_add_target(self, hetzner_client, response_add_target, bound_load_balancer, target, params): hetzner_client.request.return_value = response_add_target action = bound_load_balancer.add_target(target) params.update({ 'type': target.type, 'use_private_ip': target.use_private_ip }) hetzner_client.request.assert_called_with( json=params, url="/load_balancers/14/actions/add_target", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "add_target" @pytest.mark.parametrize( "target,params", [(LoadBalancerTarget( type="server", server=Server(id=1), use_private_ip=True), { 'server': { "id": 1 } }), (LoadBalancerTarget(type="ip", ip=LoadBalancerTargetIP(ip="127.0.0.1")), { 'ip': { "ip": "127.0.0.1" } }), (LoadBalancerTarget(type="label_selector", label_selector=LoadBalancerTargetLabelSelector( selector="abc=def")), { 'label_selector': { "selector": "abc=def" } })]) def test_remove_target(self, hetzner_client, response_remove_target, bound_load_balancer, target, params): hetzner_client.request.return_value = response_remove_target action = bound_load_balancer.remove_target(target) params.update({'type': target.type}) hetzner_client.request.assert_called_with( json=params, url="/load_balancers/14/actions/remove_target", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "remove_target" def test_update_service(self, hetzner_client, response_update_service, bound_load_balancer): hetzner_client.request.return_value = response_update_service new_health_check = LoadBalancerHealthCheck(protocol='http', port=13, interval=1, timeout=1, retries=1) service = LoadBalancerService(listen_port=12, health_check=new_health_check) action = bound_load_balancer.update_service(service) hetzner_client.request.assert_called_with( json={ 'listen_port': 12, 'health_check': { 'protocol': 'http', 'port': 13, 'interval': 1, 'timeout': 1, 'retries': 1 } }, url="/load_balancers/14/actions/update_service", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "update_service" def test_change_algorithm(self, hetzner_client, response_change_algorithm, bound_load_balancer): hetzner_client.request.return_value = response_change_algorithm algorithm = LoadBalancerAlgorithm(type="round_robin") action = bound_load_balancer.change_algorithm(algorithm) hetzner_client.request.assert_called_with( json={'type': 'round_robin'}, url="/load_balancers/14/actions/change_algorithm", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "change_algorithm" def test_change_protection(self, hetzner_client, response_change_protection, bound_load_balancer): hetzner_client.request.return_value = response_change_protection action = bound_load_balancer.change_protection(delete=True) hetzner_client.request.assert_called_with( json={'delete': True}, url="/load_balancers/14/actions/change_protection", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "change_protection" def test_enable_public_interface(self, response_enable_public_interface, hetzner_client, bound_load_balancer): hetzner_client.request.return_value = response_enable_public_interface action = bound_load_balancer.enable_public_interface() hetzner_client.request.assert_called_with( url="/load_balancers/14/actions/enable_public_interface", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "enable_public_interface" def test_disable_public_interface(self, response_disable_public_interface, hetzner_client, bound_load_balancer): hetzner_client.request.return_value = response_disable_public_interface action = bound_load_balancer.disable_public_interface() hetzner_client.request.assert_called_with( url="/load_balancers/14/actions/disable_public_interface", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "disable_public_interface" def test_attach_to_network(self, response_attach_load_balancer_to_network, hetzner_client, bound_load_balancer): hetzner_client.request.return_value = response_attach_load_balancer_to_network action = bound_load_balancer.attach_to_network(Network(id=1)) hetzner_client.request.assert_called_with( json={"network": 1}, url="/load_balancers/14/actions/attach_to_network", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "attach_to_network" def test_detach_from_network(self, response_detach_from_network, hetzner_client, bound_load_balancer): hetzner_client.request.return_value = response_detach_from_network action = bound_load_balancer.detach_from_network(Network(id=1)) hetzner_client.request.assert_called_with( json={"network": 1}, url="/load_balancers/14/actions/detach_from_network", method="POST") assert action.id == 13 assert action.progress == 100 assert action.command == "detach_from_network" def test_change_type(self, hetzner_client, bound_load_balancer, generic_action): hetzner_client.request.return_value = generic_action action = bound_load_balancer.change_type(LoadBalancerType(name="lb21")) hetzner_client.request.assert_called_with( url="/load_balancers/14/actions/change_type", method="POST", json={"load_balancer_type": "lb21"}) assert action.id == 1 assert action.progress == 0
class TestServersClient(object): @pytest.fixture() def servers_client(self): return ServersClient(client=mock.MagicMock()) def test_get_by_id(self, servers_client, response_simple_server): servers_client._client.request.return_value = response_simple_server bound_server = servers_client.get_by_id(1) servers_client._client.request.assert_called_with(url="/servers/1", method="GET") assert bound_server._client is servers_client assert bound_server.id == 1 assert bound_server.name == "my-server" @pytest.mark.parametrize("params", [{ 'name': "server1", 'label_selector': "label1", 'page': 1, 'per_page': 10 }, { 'name': "" }, {}]) def test_get_list(self, servers_client, response_simple_servers, params): servers_client._client.request.return_value = response_simple_servers result = servers_client.get_list(**params) servers_client._client.request.assert_called_with(url="/servers", method="GET", params=params) bound_servers = result.servers assert result.meta is None assert len(bound_servers) == 2 bound_server1 = bound_servers[0] bound_server2 = bound_servers[1] assert bound_server1._client is servers_client assert bound_server1.id == 1 assert bound_server1.name == "my-server" assert bound_server2._client is servers_client assert bound_server2.id == 2 assert bound_server2.name == "my-server2" @pytest.mark.parametrize("params", [{ 'name': "server1", 'label_selector': "label1" }, {}]) def test_get_all(self, servers_client, response_simple_servers, params): servers_client._client.request.return_value = response_simple_servers bound_servers = servers_client.get_all(**params) params.update({'page': 1, 'per_page': 50}) servers_client._client.request.assert_called_with(url="/servers", method="GET", params=params) assert len(bound_servers) == 2 bound_server1 = bound_servers[0] bound_server2 = bound_servers[1] assert bound_server1._client is servers_client assert bound_server1.id == 1 assert bound_server1.name == "my-server" assert bound_server2._client is servers_client assert bound_server2.id == 2 assert bound_server2.name == "my-server2" def test_get_by_name(self, servers_client, response_simple_servers): servers_client._client.request.return_value = response_simple_servers bound_server = servers_client.get_by_name("my-server") params = {'name': "my-server"} servers_client._client.request.assert_called_with(url="/servers", method="GET", params=params) assert bound_server._client is servers_client assert bound_server.id == 1 assert bound_server.name == "my-server" def test_create_with_datacenter(self, servers_client, response_create_simple_server): servers_client._client.request.return_value = response_create_simple_server response = servers_client.create("my-server", server_type=ServerType(name="cx11"), image=Image(id=4711), datacenter=Datacenter(id=1)) servers_client._client.request.assert_called_with( url="/servers", method="POST", json={ 'name': "my-server", 'server_type': "cx11", 'image': 4711, 'datacenter': 1, "start_after_create": True }) bound_server = response.server assert bound_server._client is servers_client assert bound_server.id == 1 assert bound_server.name == "my-server" def test_create_with_location(self, servers_client, response_create_simple_server): servers_client._client.request.return_value = response_create_simple_server response = servers_client.create("my-server", server_type=ServerType(name="cx11"), image=Image(name="ubuntu-20.04"), location=Location(name="fsn1")) servers_client._client.request.assert_called_with( url="/servers", method="POST", json={ 'name': "my-server", 'server_type': "cx11", 'image': "ubuntu-20.04", 'location': "fsn1", "start_after_create": True }) bound_server = response.server bound_action = response.action assert bound_server._client is servers_client assert bound_server.id == 1 assert bound_server.name == "my-server" assert isinstance(bound_action, BoundAction) assert bound_action._client == servers_client._client.actions assert bound_action.id == 1 assert bound_action.command == "create_server" def test_create_with_volumes(self, servers_client, response_create_simple_server): servers_client._client.request.return_value = response_create_simple_server volumes = [Volume(id=1), BoundVolume(mock.MagicMock(), dict(id=2))] response = servers_client.create("my-server", server_type=ServerType(name="cx11"), image=Image(id=4711), volumes=volumes, start_after_create=False) servers_client._client.request.assert_called_with( url="/servers", method="POST", json={ 'name': "my-server", 'server_type': "cx11", 'image': 4711, 'volumes': [1, 2], "start_after_create": False }) bound_server = response.server bound_action = response.action next_actions = response.next_actions root_password = response.root_password assert root_password == "YItygq1v3GYjjMomLaKc" assert bound_server._client is servers_client assert bound_server.id == 1 assert bound_server.name == "my-server" assert isinstance(bound_action, BoundAction) assert bound_action._client == servers_client._client.actions assert bound_action.id == 1 assert bound_action.command == "create_server" assert next_actions[0].id == 13 def test_create_with_networks(self, servers_client, response_create_simple_server): servers_client._client.request.return_value = response_create_simple_server networks = [Network(id=1), BoundNetwork(mock.MagicMock(), dict(id=2))] response = servers_client.create("my-server", server_type=ServerType(name="cx11"), image=Image(id=4711), networks=networks, start_after_create=False) servers_client._client.request.assert_called_with( url="/servers", method="POST", json={ 'name': "my-server", 'server_type': "cx11", 'image': 4711, 'networks': [1, 2], "start_after_create": False }) bound_server = response.server bound_action = response.action next_actions = response.next_actions root_password = response.root_password assert root_password == "YItygq1v3GYjjMomLaKc" assert bound_server._client is servers_client assert bound_server.id == 1 assert bound_server.name == "my-server" assert isinstance(bound_action, BoundAction) assert bound_action._client == servers_client._client.actions assert bound_action.id == 1 assert bound_action.command == "create_server" assert next_actions[0].id == 13 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_get_actions_list(self, servers_client, server, response_get_actions): servers_client._client.request.return_value = response_get_actions result = servers_client.get_actions_list(server) servers_client._client.request.assert_called_with( url="/servers/1/actions", method="GET", params={}) actions = result.actions assert result.meta is None assert len(actions) == 1 assert isinstance(actions[0], BoundAction) assert actions[0]._client == servers_client._client.actions assert actions[0].id == 13 assert actions[0].command == "start_server" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_update(self, servers_client, server, response_update_server): servers_client._client.request.return_value = response_update_server server = servers_client.update(server, name="new-name", labels={}) servers_client._client.request.assert_called_with(url="/servers/1", method="PUT", json={ "name": "new-name", "labels": {} }) assert server.id == 14 assert server.name == "new-name" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_delete(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.delete(server) servers_client._client.request.assert_called_with(url="/servers/1", method="DELETE") assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_power_off(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.power_off(server) servers_client._client.request.assert_called_with( url="/servers/1/actions/poweroff", method="POST") assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_power_on(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.power_on(server) servers_client._client.request.assert_called_with( url="/servers/1/actions/poweron", method="POST") assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_reboot(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.reboot(server) servers_client._client.request.assert_called_with( url="/servers/1/actions/reboot", method="POST") assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_reset(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.reset(server) servers_client._client.request.assert_called_with( url="/servers/1/actions/reset", method="POST") assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_shutdown(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.shutdown(server) servers_client._client.request.assert_called_with( url="/servers/1/actions/shutdown", method="POST") assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_reset_password(self, servers_client, server, response_server_reset_password): servers_client._client.request.return_value = response_server_reset_password response = servers_client.reset_password(server) servers_client._client.request.assert_called_with( url="/servers/1/actions/reset_password", method="POST") assert response.action.id == 1 assert response.action.progress == 0 assert response.root_password == "YItygq1v3GYjjMomLaKc" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_change_type_with_server_type_name(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.change_type(server, ServerType(name="cx11"), upgrade_disk=True) servers_client._client.request.assert_called_with( url="/servers/1/actions/change_type", method="POST", json={ "server_type": "cx11", "upgrade_disk": True }) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_change_type_with_server_type_id(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.change_type(server, ServerType(id=1), upgrade_disk=True) servers_client._client.request.assert_called_with( url="/servers/1/actions/change_type", method="POST", json={ "server_type": 1, "upgrade_disk": True }) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_change_type_with_blank_server_type(self, servers_client, server): with pytest.raises(ValueError) as e: servers_client.change_type(server, ServerType(), upgrade_disk=True) assert str(e.value) == "id or name must be set" servers_client._client.request.assert_not_called() @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_enable_rescue(self, servers_client, server, response_server_enable_rescue): servers_client._client.request.return_value = response_server_enable_rescue response = servers_client.enable_rescue(server, "linux64", [2323]) servers_client._client.request.assert_called_with( url="/servers/1/actions/enable_rescue", method="POST", json={ "type": "linux64", "ssh_keys": [2323] }) assert response.action.id == 1 assert response.action.progress == 0 assert response.root_password == "YItygq1v3GYjjMomLaKc" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_disable_rescue(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.disable_rescue(server) servers_client._client.request.assert_called_with( url="/servers/1/actions/disable_rescue", method="POST") assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_create_image(self, servers_client, server, response_server_create_image): servers_client._client.request.return_value = response_server_create_image response = servers_client.create_image(server, description="my image", type="snapshot", labels={"key": "value"}) servers_client._client.request.assert_called_with( url="/servers/1/actions/create_image", method="POST", json={ "description": "my image", "type": "snapshot", "labels": { "key": "value" } }) assert response.action.id == 1 assert response.action.progress == 0 assert response.image.description == "my image" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_rebuild(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.rebuild(server, Image(name="ubuntu-20.04")) servers_client._client.request.assert_called_with( url="/servers/1/actions/rebuild", method="POST", json={"image": "ubuntu-20.04"}) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_enable_backup(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.enable_backup(server) servers_client._client.request.assert_called_with( url="/servers/1/actions/enable_backup", method="POST") assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_disable_backup(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.disable_backup(server) servers_client._client.request.assert_called_with( url="/servers/1/actions/disable_backup", method="POST") assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_attach_iso(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.attach_iso( server, Iso(name="FreeBSD-11.0-RELEASE-amd64-dvd1")) servers_client._client.request.assert_called_with( url="/servers/1/actions/attach_iso", method="POST", json={"iso": "FreeBSD-11.0-RELEASE-amd64-dvd1"}) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_detach_iso(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.detach_iso(server) servers_client._client.request.assert_called_with( url="/servers/1/actions/detach_iso", method="POST") assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_change_dns_ptr(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.change_dns_ptr(server, "1.2.3.4", "example.com") servers_client._client.request.assert_called_with( url="/servers/1/actions/change_dns_ptr", method="POST", json={ "ip": "1.2.3.4", "dns_ptr": "example.com" }) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_change_protection(self, servers_client, server, generic_action): servers_client._client.request.return_value = generic_action action = servers_client.change_protection(server, True, True) servers_client._client.request.assert_called_with( url="/servers/1/actions/change_protection", method="POST", json={ "delete": True, "rebuild": True }) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_request_console(self, servers_client, server, response_server_request_console): servers_client._client.request.return_value = response_server_request_console response = servers_client.request_console(server) servers_client._client.request.assert_called_with( url="/servers/1/actions/request_console", method="POST") assert response.action.id == 1 assert response.action.progress == 0 assert response.wss_url == "wss://console.hetzner.cloud/?server_id=1&token=3db32d15-af2f-459c-8bf8-dee1fd05f49c" assert response.password == "9MQaTg2VAGI0FIpc10k3UpRXcHj2wQ6x" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) @pytest.mark.parametrize( "network", [Network(id=4711), BoundNetwork(mock.MagicMock(), dict(id=4711))]) def test_attach_to_network(self, servers_client, server, network, response_attach_to_network): servers_client._client.request.return_value = response_attach_to_network action = servers_client.attach_to_network(server, network, "10.0.1.1", ["10.0.1.2", "10.0.1.3"]) servers_client._client.request.assert_called_with( url="/servers/1/actions/attach_to_network", method="POST", json={ "network": 4711, "ip": "10.0.1.1", "alias_ips": ["10.0.1.2", "10.0.1.3"] }) assert action.id == 1 assert action.progress == 0 assert action.command == "attach_to_network" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) @pytest.mark.parametrize( "network", [Network(id=4711), BoundNetwork(mock.MagicMock(), dict(id=4711))]) def test_detach_from_network(self, servers_client, server, network, response_detach_from_network): servers_client._client.request.return_value = response_detach_from_network action = servers_client.detach_from_network(server, network) servers_client._client.request.assert_called_with( url="/servers/1/actions/detach_from_network", method="POST", json={"network": 4711}) assert action.id == 1 assert action.progress == 0 assert action.command == "detach_from_network" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) @pytest.mark.parametrize( "network", [Network(id=4711), BoundNetwork(mock.MagicMock(), dict(id=4711))]) def test_change_alias_ips(self, servers_client, server, network, response_change_alias_ips): servers_client._client.request.return_value = response_change_alias_ips action = servers_client.change_alias_ips(server, network, ["10.0.1.2", "10.0.1.3"]) servers_client._client.request.assert_called_with( url="/servers/1/actions/change_alias_ips", method="POST", json={ "network": 4711, "alias_ips": ["10.0.1.2", "10.0.1.3"] }) assert action.id == 1 assert action.progress == 0 assert action.command == "change_alias_ips"
class TestVolumesClient(object): @pytest.fixture() def volumes_client(self): return VolumesClient(client=mock.MagicMock()) def test_get_by_id(self, volumes_client, volume_response): volumes_client._client.request.return_value = volume_response bound_volume = volumes_client.get_by_id(1) volumes_client._client.request.assert_called_with(url="/volumes/1", method="GET") assert bound_volume._client is volumes_client assert bound_volume.id == 1 assert bound_volume.name == "database-storage" @pytest.mark.parametrize( "params", [ {'label_selector': "label1", 'page': 1, 'per_page': 10}, {} ] ) def test_get_list(self, volumes_client, two_volumes_response, params): volumes_client._client.request.return_value = two_volumes_response result = volumes_client.get_list(**params) volumes_client._client.request.assert_called_with(url="/volumes", method="GET", params=params) bound_volumes = result.volumes assert result.meta is None assert len(bound_volumes) == 2 bound_volume1 = bound_volumes[0] bound_volume2 = bound_volumes[1] assert bound_volume1._client is volumes_client assert bound_volume1.id == 1 assert bound_volume1.name == "database-storage" assert bound_volume2._client is volumes_client assert bound_volume2.id == 2 assert bound_volume2.name == "vault-storage" @pytest.mark.parametrize("params", [{'label_selector': "label1"}]) def test_get_all(self, volumes_client, two_volumes_response, params): volumes_client._client.request.return_value = two_volumes_response bound_volumes = volumes_client.get_all(**params) params.update({'page': 1, 'per_page': 50}) volumes_client._client.request.assert_called_with(url="/volumes", method="GET", params=params) assert len(bound_volumes) == 2 bound_volume1 = bound_volumes[0] bound_volume2 = bound_volumes[1] assert bound_volume1._client is volumes_client assert bound_volume1.id == 1 assert bound_volume1.name == "database-storage" assert bound_volume2._client is volumes_client assert bound_volume2.id == 2 assert bound_volume2.name == "vault-storage" def test_get_by_name(self, volumes_client, one_volumes_response): volumes_client._client.request.return_value = one_volumes_response bound_volume = volumes_client.get_by_name("database-storage") params = {'name': "database-storage"} volumes_client._client.request.assert_called_with(url="/volumes", method="GET", params=params) assert bound_volume._client is volumes_client assert bound_volume.id == 1 assert bound_volume.name == "database-storage" def test_create_with_location(self, volumes_client, volume_create_response): volumes_client._client.request.return_value = volume_create_response response = volumes_client.create( 100, "database-storage", location=Location(name="location"), automount=False, format="xfs" ) volumes_client._client.request.assert_called_with( url="/volumes", method="POST", json={ 'name': "database-storage", 'size': 100, 'location': "location", 'automount': False, 'format': "xfs" } ) bound_volume = response.volume action = response.action next_actions = response.next_actions assert bound_volume._client is volumes_client assert bound_volume.id == 4711 assert bound_volume.name == "database-storage" assert action.id == 13 assert next_actions[0].command == "start_server" @pytest.mark.parametrize("server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_create_with_server(self, volumes_client, server, volume_create_response): volumes_client._client.request.return_value = volume_create_response volumes_client.create( 100, "database-storage", server=server, automount=False, format="xfs" ) volumes_client._client.request.assert_called_with( url="/volumes", method="POST", json={ 'name': "database-storage", 'size': 100, 'server': 1, 'automount': False, 'format': "xfs" } ) def test_create_negative_size(self, volumes_client): with pytest.raises(ValueError) as e: volumes_client.create( -100, "database-storage", location=Location(name="location") ) assert str(e.value) == "size must be greater than 0" volumes_client._client.request.assert_not_called() @pytest.mark.parametrize("location,server", [(None, None), ("location", Server(id=1))]) def test_create_wrong_location_server_combination(self, volumes_client, location, server): with pytest.raises(ValueError) as e: volumes_client.create( 100, "database-storage", location=location, server=server ) assert str(e.value) == "only one of server or location must be provided" volumes_client._client.request.assert_not_called() @pytest.mark.parametrize("volume", [Volume(id=1), BoundVolume(mock.MagicMock(), dict(id=1))]) def test_get_actions_list(self, volumes_client, volume, response_get_actions): volumes_client._client.request.return_value = response_get_actions result = volumes_client.get_actions_list(volume, sort="id") volumes_client._client.request.assert_called_with(url="/volumes/1/actions", method="GET", params={"sort": "id"}) actions = result.actions assert len(actions) == 1 assert isinstance(actions[0], BoundAction) assert actions[0]._client == volumes_client._client.actions assert actions[0].id == 13 assert actions[0].command == "attach_volume" @pytest.mark.parametrize("volume", [Volume(id=1), BoundVolume(mock.MagicMock(), dict(id=1))]) def test_update(self, volumes_client, volume, response_update_volume): volumes_client._client.request.return_value = response_update_volume volume = volumes_client.update(volume, name="new-name") volumes_client._client.request.assert_called_with(url="/volumes/1", method="PUT", json={"name": "new-name"}) assert volume.id == 4711 assert volume.name == "new-name" @pytest.mark.parametrize("volume", [Volume(id=1), BoundVolume(mock.MagicMock(), dict(id=1))]) def test_change_protection(self, volumes_client, volume, generic_action): volumes_client._client.request.return_value = generic_action action = volumes_client.change_protection(volume, True) volumes_client._client.request.assert_called_with(url="/volumes/1/actions/change_protection", method="POST", json={"delete": True}) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize("volume", [Volume(id=1), BoundVolume(mock.MagicMock(), dict(id=1))]) def test_delete(self, volumes_client, volume, generic_action): volumes_client._client.request.return_value = generic_action delete_success = volumes_client.delete(volume) volumes_client._client.request.assert_called_with(url="/volumes/1", method="DELETE") assert delete_success is True @pytest.mark.parametrize("server,volume", [(Server(id=1), Volume(id=12)), (BoundServer(mock.MagicMock(), dict(id=1)), BoundVolume(mock.MagicMock(), dict(id=12)))]) def test_attach(self, volumes_client, server, volume, generic_action): volumes_client._client.request.return_value = generic_action action = volumes_client.attach(volume, server) volumes_client._client.request.assert_called_with( url="/volumes/12/actions/attach", method="POST", json={"server": 1} ) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize("volume", [Volume(id=12), BoundVolume(mock.MagicMock(), dict(id=12))]) def test_detach(self, volumes_client, volume, generic_action): volumes_client._client.request.return_value = generic_action action = volumes_client.detach(volume) volumes_client._client.request.assert_called_with( url="/volumes/12/actions/detach", method="POST" ) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize("volume", [Volume(id=12), BoundVolume(mock.MagicMock(), dict(id=12))]) def test_resize(self, volumes_client, volume, generic_action): volumes_client._client.request.return_value = generic_action action = volumes_client.resize(volume, 50) volumes_client._client.request.assert_called_with( url="/volumes/12/actions/resize", method="POST", json={"size": 50} ) assert action.id == 1 assert action.progress == 0
class TestBoundVolume(object): @pytest.fixture() def bound_volume(self, hetzner_client): return BoundVolume(client=hetzner_client.volumes, data=dict(id=14)) def test_bound_volume_init(self, volume_response): bound_volume = BoundVolume( client=mock.MagicMock(), data=volume_response['volume'] ) assert bound_volume.id == 1 assert bound_volume.created == isoparse("2016-01-30T23:50:11+00:00") assert bound_volume.name == "database-storage" assert isinstance(bound_volume.server, BoundServer) assert bound_volume.server.id == 12 assert bound_volume.size == 42 assert bound_volume.linux_device == "/dev/disk/by-id/scsi-0HC_Volume_4711" assert bound_volume.protection == {"delete": False} assert bound_volume.labels == {} assert bound_volume.status == "available" assert isinstance(bound_volume.location, BoundLocation) assert bound_volume.location.id == 1 assert bound_volume.location.name == "fsn1" assert bound_volume.location.description == "Falkenstein DC Park 1" assert bound_volume.location.country == "DE" assert bound_volume.location.city == "Falkenstein" assert bound_volume.location.latitude == 50.47612 assert bound_volume.location.longitude == 12.370071 def test_get_actions(self, hetzner_client, bound_volume, response_get_actions): hetzner_client.request.return_value = response_get_actions actions = bound_volume.get_actions(sort="id") hetzner_client.request.assert_called_with(url="/volumes/14/actions", method="GET", params={"page": 1, "per_page": 50, "sort": "id"}) assert len(actions) == 1 assert isinstance(actions[0], BoundAction) assert actions[0].id == 13 assert actions[0].command == "attach_volume" def test_update(self, hetzner_client, bound_volume, response_update_volume): hetzner_client.request.return_value = response_update_volume volume = bound_volume.update(name="new-name") hetzner_client.request.assert_called_with(url="/volumes/14", method="PUT", json={"name": "new-name"}) assert volume.id == 4711 assert volume.name == "new-name" def test_delete(self, hetzner_client, bound_volume, generic_action): hetzner_client.request.return_value = generic_action delete_success = bound_volume.delete() hetzner_client.request.assert_called_with(url="/volumes/14", method="DELETE") assert delete_success is True def test_change_protection(self, hetzner_client, bound_volume, generic_action): hetzner_client.request.return_value = generic_action action = bound_volume.change_protection(True) hetzner_client.request.assert_called_with(url="/volumes/14/actions/change_protection", method="POST", json={"delete": True}) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize("server", (Server(id=1), BoundServer(mock.MagicMock(), dict(id=1)))) def test_attach(self, hetzner_client, bound_volume, server, generic_action): hetzner_client.request.return_value = generic_action action = bound_volume.attach(server) hetzner_client.request.assert_called_with( url="/volumes/14/actions/attach", method="POST", json={"server": 1} ) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize("server", (Server(id=1), BoundServer(mock.MagicMock(), dict(id=1)))) def test_attach_with_automount(self, hetzner_client, bound_volume, server, generic_action): hetzner_client.request.return_value = generic_action action = bound_volume.attach(server, False) hetzner_client.request.assert_called_with( url="/volumes/14/actions/attach", method="POST", json={"server": 1, "automount": False} ) assert action.id == 1 assert action.progress == 0 def test_detach(self, hetzner_client, bound_volume, generic_action): hetzner_client.request.return_value = generic_action action = bound_volume.detach() hetzner_client.request.assert_called_with( url="/volumes/14/actions/detach", method="POST" ) assert action.id == 1 assert action.progress == 0 def test_resize(self, hetzner_client, bound_volume, generic_action): hetzner_client.request.return_value = generic_action action = bound_volume.resize(50) hetzner_client.request.assert_called_with( url="/volumes/14/actions/resize", method="POST", json={"size": 50} ) assert action.id == 1 assert action.progress == 0
name="Volume2", location=server2.location) volume1 = response1.volume volume2 = response2.volume # Attach volume to server client.volumes.attach(server1, volume1) client.volumes.attach(server2, volume2) # Detach second volume client.volumes.detach(volume2) # Poweroff 2nd server client.servers.power_off(server2) # Create one more volume and attach it to server with id=33 server33 = Server(id=33) response = client.volumes.create(size=33, name="Volume33", server=server33) print(response.action.status) # Create one more server and attach 2 volumes to it client.servers.create("Server3", server_type=ServerType(name="cx11"), image=Image(id=4711), volumes=[Volume(id=221), Volume(id=222)])
class TestServersClient(object): def test_get_by_id(self, hetzner_client): server = hetzner_client.servers.get_by_id(42) assert server.id == 42 assert server.volumes == [] assert server.server_type.id == 1 assert server.datacenter.id == 1 assert server.image.id == 4711 def test_get_by_name(self, hetzner_client): server = hetzner_client.servers.get_by_name("my-server") assert server.id == 42 assert server.name == "my-server" assert server.volumes == [] assert server.server_type.id == 1 assert server.datacenter.id == 1 assert server.image.id == 4711 def test_get_list(self, hetzner_client): result = hetzner_client.servers.get_list() servers = result.servers assert servers[0].id == 42 assert servers[0].volumes == [] assert servers[0].server_type.id == 1 assert servers[0].datacenter.id == 1 assert servers[0].image.id == 4711 def test_create(self, hetzner_client): response = hetzner_client.servers.create( "my-server", server_type=ServerType(name="cx11"), image=Image(name="ubuntu-16.04"), ssh_keys=[SSHKey(name="my-ssh-key")], volumes=[Volume(id=1)], user_data= "#cloud-config\\nruncmd:\\n- [touch, /root/cloud-init-worked]\\n", location=Location(name="nbg1"), automount=False) server = response.server action = response.action next_actions = response.next_actions root_password = response.root_password assert server.id == 42 assert server.volumes == [] assert server.server_type.id == 1 assert server.datacenter.id == 1 assert server.image.id == 4711 assert action.id == 1 assert action.command == "create_server" assert len(next_actions) == 1 assert next_actions[0].id == 13 assert next_actions[0].command == "start_server" assert root_password == "YItygq1v3GYjjMomLaKc" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_get_actions_list(self, hetzner_client, server): result = hetzner_client.servers.get_actions_list(server) actions = result.actions assert len(actions) == 1 assert actions[0].id == 13 assert actions[0].command == "start_server" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_update(self, hetzner_client, server): server = hetzner_client.servers.update(server, name="new-name", labels={}) assert server.id == 42 assert server.name == "new-name" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_delete(self, hetzner_client, server): action = hetzner_client.servers.delete(server) assert action.id == 13 assert action.command == "delete_server" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_power_off(self, hetzner_client, server): action = hetzner_client.servers.power_off(server) assert action.id == 13 assert action.command == "stop_server" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_power_on(self, hetzner_client, server): action = hetzner_client.servers.power_on(server) assert action.id == 13 assert action.command == "start_server" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_reboot(self, hetzner_client, server): action = hetzner_client.servers.reboot(server) assert action.id == 13 assert action.command == "reboot_server" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_reset(self, hetzner_client, server): action = hetzner_client.servers.reset(server) assert action.id == 13 assert action.command == "reset_server" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_shutdown(self, hetzner_client, server): action = hetzner_client.servers.shutdown(server) assert action.id == 13 assert action.command == "shutdown_server" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_reset_password(self, hetzner_client, server): response = hetzner_client.servers.reset_password(server) assert response.action.id == 13 assert response.action.command == "reset_password" assert response.root_password == "zCWbFhnu950dUTko5f40" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_change_type(self, hetzner_client, server): action = hetzner_client.servers.change_type(server, ServerType(name="cx11"), upgrade_disk=True) assert action.id == 13 assert action.command == "change_server_type" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_enable_rescue(self, hetzner_client, server): response = hetzner_client.servers.enable_rescue(server, type="linux64", ssh_keys=[2323]) assert response.action.id == 13 assert response.action.command == "enable_rescue" assert response.root_password == "zCWbFhnu950dUTko5f40" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_disable_rescue(self, hetzner_client, server): action = hetzner_client.servers.disable_rescue(server) assert action.id == 13 assert action.command == "disable_rescue" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_create_image(self, hetzner_client, server): response = hetzner_client.servers.create_image(server, description="my image", type="snapshot") assert response.action.id == 13 assert response.action.command == "create_image" assert response.image.description == "my image" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_rebuild(self, hetzner_client, server): action = hetzner_client.servers.rebuild(server, Image(name="ubuntu-16.04")) assert action.id == 13 assert action.command == "rebuild_server" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_enable_backup(self, hetzner_client, server): action = hetzner_client.servers.enable_backup(server) assert action.id == 13 assert action.command == "enable_backup" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_disable_backup(self, hetzner_client, server): action = hetzner_client.servers.disable_backup(server) assert action.id == 13 assert action.command == "disable_backup" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_attach_iso(self, hetzner_client, server): action = hetzner_client.servers.attach_iso( server, Iso(name="FreeBSD-11.0-RELEASE-amd64-dvd1")) assert action.id == 13 assert action.command == "attach_iso" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_detach_iso(self, hetzner_client, server): action = hetzner_client.servers.detach_iso(server) assert action.id == 13 assert action.command == "detach_iso" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_change_dns_ptr(self, hetzner_client, server): action = hetzner_client.servers.change_dns_ptr(server, "1.2.3.4", "example.com") assert action.id == 13 assert action.command == "change_dns_ptr" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_change_protection(self, hetzner_client, server): action = hetzner_client.servers.change_protection(server, delete=True, rebuild=True) assert action.id == 13 assert action.command == "change_protection" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_request_console(self, hetzner_client, server): response = hetzner_client.servers.request_console(server) assert response.action.id == 13 assert response.action.command == "request_console" assert response.wss_url == "wss://console.hetzner.cloud/?server_id=1&token=3db32d15-af2f-459c-8bf8-dee1fd05f49c" assert response.password == "9MQaTg2VAGI0FIpc10k3UpRXcHj2wQ6x" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) @pytest.mark.parametrize( "network", [Network(id=4711), BoundNetwork(mock.MagicMock(), dict(id=4711))]) def test_attach_to_network(self, hetzner_client, server, network): action = hetzner_client.servers.attach_to_network( server, network, ip="10.0.1.1", alias_ips=["10.0.1.2"]) assert action.id == 13 assert action.command == "attach_to_network" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) @pytest.mark.parametrize( "network", [Network(id=4711), BoundNetwork(mock.MagicMock(), dict(id=4711))]) def test_detach_from_network(self, hetzner_client, server, network): action = hetzner_client.servers.detach_from_network(server, network) assert action.id == 13 assert action.command == "detach_from_network" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) @pytest.mark.parametrize( "network", [Network(id=4711), BoundNetwork(mock.MagicMock(), dict(id=4711))]) def test_change_alias_ips(self, hetzner_client, server, network): action = hetzner_client.servers.change_alias_ips( server, network, alias_ips=["10.0.1.2"]) assert action.id == 13 assert action.command == "change_alias_ips"
class TestFloatingIPsClient(object): def test_get_by_id(self, hetzner_client): bound_floating_ip = hetzner_client.floating_ips.get_by_id(4711) assert bound_floating_ip.id == 4711 assert bound_floating_ip.description == "Web Frontend" assert bound_floating_ip.type == "ipv4" def test_get_list(self, hetzner_client): result = hetzner_client.floating_ips.get_list() bound_floating_ips = result.floating_ips assert bound_floating_ips[0].id == 4711 assert bound_floating_ips[0].description == "Web Frontend" assert bound_floating_ips[0].type == "ipv4" @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_create(self, hetzner_client, server): response = hetzner_client.floating_ips.create( type="ipv4", description="Web Frontend", # home_location=Location(description="fsn1"), server=server) floating_ip = response.floating_ip action = response.action assert floating_ip.id == 4711 assert floating_ip.description == "Web Frontend" assert floating_ip.type == "ipv4" assert action.id == 13 assert action.command == "assign_floating_ip" @pytest.mark.parametrize( "floating_ip", [FloatingIP(id=1), BoundFloatingIP(mock.MagicMock(), dict(id=1))]) def test_get_actions(self, hetzner_client, floating_ip): actions = hetzner_client.floating_ips.get_actions(floating_ip) assert len(actions) == 1 assert actions[0].id == 13 assert actions[0].command == "assign_floating_ip" @pytest.mark.parametrize( "floating_ip", [FloatingIP(id=1), BoundFloatingIP(mock.MagicMock(), dict(id=1))]) def test_update(self, hetzner_client, floating_ip): floating_ip = hetzner_client.floating_ips.update( floating_ip, description="New description", labels={}) assert floating_ip.id == 4711 assert floating_ip.description == "New description" @pytest.mark.parametrize( "floating_ip", [FloatingIP(id=1), BoundFloatingIP(mock.MagicMock(), dict(id=1))]) def test_delete(self, hetzner_client, floating_ip): delete_success = hetzner_client.floating_ips.delete(floating_ip) assert delete_success is True @pytest.mark.parametrize( "server,floating_ip", [(Server(id=43), FloatingIP(id=4711)), (BoundServer(mock.MagicMock(), dict(id=43)), BoundFloatingIP(mock.MagicMock(), dict(id=4711)))]) def test_assign(self, hetzner_client, server, floating_ip): action = hetzner_client.floating_ips.assign(floating_ip, server) assert action.id == 13 assert action.progress == 0 assert action.command == "assign_floating_ip" @pytest.mark.parametrize("floating_ip", [ FloatingIP(id=4711), BoundFloatingIP(mock.MagicMock(), dict(id=4711)) ]) def test_unassign(self, hetzner_client, floating_ip): action = hetzner_client.floating_ips.unassign(floating_ip) assert action.id == 13 assert action.progress == 0 assert action.command == "unassign_floating_ip" @pytest.mark.parametrize("floating_ip", [ FloatingIP(id=4711), BoundFloatingIP(mock.MagicMock(), dict(id=4711)) ]) def test_change_dns_ptr(self, hetzner_client, floating_ip): action = hetzner_client.floating_ips.change_dns_ptr( floating_ip, "1.2.3.4", "server02.example.com") assert action.id == 13 assert action.progress == 0 assert action.command == "change_dns_ptr"
client = Client(token="......") serverName = "test" oldServer = client.servers.get_by_name(name=serverName) #Shut down the OldServer try: oldServer.power_off() except: print("Error when turning down the server. Confirm if the server's name is right") quit() #Get all the information of the old server oldServerType = (Server(id=oldServer).id).server_type oldServerDatacenter = (Server(id=oldServer).id).datacenter oldServerVolumes = (Server(id=oldServer).id).volumes oldServerFloatingIP = ((Server(id=oldServer).id).public_net).floating_ips sshKeys = client.ssh_keys.get_all() #Find the newest backup images = client.images.get_all(type="backup") backupNewestID = 0 for image in images: infImage = (client.images.get_by_id(image.id)) createdFrom = ((Image(id=infImage).id).created_from).name if createdFrom == serverName: idImage = infImage.id if idImage > backupNewestID:
def _handle_changed_volumes(self, defn: HetznerCloudDefinition, allow_recreate: bool) -> None: """ Detects and corrects any volume state desynchronisation. """ attached: Set[str] = {x.name for x in self.get_instance().volumes} for name in self.volumes.keys(): volume: Optional[BoundVolume] = self.get_client( ).volumes.get_by_name(name) # Detect destroyed volumes. if volume is None: if name not in defn.volumes: # we dont need it self.logger.warn( f"forgetting about volume ‘{name}’ that no longer exists" " and is no longer needed by the deployment specification" ) else: if name.startswith("nixops-" + self.depl.uuid): raise Exception( f"volume ‘{name}’ (used by {self.full_name}) no longer exists;" " run ‘nixops deploy --check’ to update resource state" ) else: raise Exception( f"volume ‘{name}’ (used by {self.full_name}) was" " manually destroyed") # Detect detached volumes. elif name not in attached: if name not in defn.volumes: # we dont need it self.logger.warn( f"forgetting about detached volume ‘{name}’ [{volume.id}]" " that is no longer needed by the deployment specification" ) else: # we do need it self.logger.warn( f"volume ‘{name}’ [{volume.id}] was manually detached;" " will reattach it") self._update_attr("volumes", name, None) # Detach existing attached volumes if required. elif name not in defn.volumes: self.logger.warn( f"detaching volume ‘{name}’ [{volume.id}] that is no longer" " needed by the deployment specification") volume.detach().wait_until_finished() self._update_attr("volumes", name, None) # Attach missing volumes. resize filesystems if required, before mounting. for name, v in defn.volumes.items(): if name not in self.volumes: # Check if it exists. resources will have been created if user ran check, # but prexisting vols which got deleted may be gone (detected in code above) volume = self.get_client().volumes.get_by_name(name) if volume is None: self.logger.warn( f"tried to attach non-NixOps managed volume ‘{name}’," " but it doesn't exist... skipping") continue elif volume.location.name != self.location: raise Exception( f"volume ‘{name}’ [{volume.id}] is in a different location" " to {self.full_name}; attempting to attach it will fail." ) elif (volume.server and volume.server.id != self.vm_id and self.depl.logger.confirm( f"volume ‘{name}’ is in use by instance ‘{volume.server.id}’," " are you sure you want to attach this volume?") ): # noqa: E124 self.logger.log( f"detaching volume ‘{name}’ from instance ‘{volume.server.id}’..." ) volume.detach().wait_until_finished() volume.server = None # Attach volume. self.logger.log(f"attaching volume ‘{name}’ [{volume.id}]... ") volume.attach(Server(self.vm_id)).wait_until_finished() # Wait until the device is visible in the instance. v["device"] = self.get_udev_name(volume.id) def check_device() -> bool: return 0 == self.run_command(f"test -e {v['device']}", check=False) if not check_wait( check_device, initial=1, max_tries=10, exception=False): # If stopping times out, then do an unclean shutdown. self.logger.log_end("(timed out)") self.logger.log(f"can't find device ‘{v['device']}’...") self.logger.log("available devices:") self.run_command("lsblk") raise Exception("operation timed out") else: self._update_attr("volumes", name, v) self.logger.log_end("") # Grow filesystems on resource based volumes. # We want to grow the fs when its volume gets resized, but if the # volume isn't attached to any server at the time, thats not possible. # Blindly trying to grow all volumes when mounting them just in case # they got resized while they were orphaned is bad. Workaround: # the needsFSResize attribute of VolumeState is set when the volume # gets resized by NixOps. When attaching a volume NixOps will use this # flag to decide whether to grow the filesystem. if name.startswith("nixops-" + self.depl.uuid): res = self.depl.get_typed_resource(name[44:], "hetznercloud-volume", VolumeState) # get correct option definitions for volume resources v["size"] = res._state["size"] v["fsType"] = res._state["fsType"] v["device"] = self.get_udev_name(res._state["resourceId"]) question = ( f"volume {name} was resized, do you wish to grow its" " filesystem to fill the space?") op = (f"umount {v['device']} ;" f"e2fsck -fy {v['device']} &&" f"resize2fs {v['device']}") if (v["fsType"] == "ext4" and res.needsFSResize and self.depl.logger.confirm(question) and self.run_command(op, check=False) == 0): with res.depl._db: res.needsFSResize = False self._update_attr("volumes", name, v) if v["mountPoint"]: volume = self.get_client().volumes.get_by_name(name) v["device"] = self.get_udev_name(volume.id) self._update_attr("volumes", name, v)
def start(self) -> None: self.logger.log_start(f"powering on {self.full_name}...") self.wait_on_action(self.get_client().servers.power_on( Server(self.vm_id))) self.wait_for_ssh() self.state = self.UP
class TestBoundFloatingIP(object): @pytest.fixture() def bound_floating_ip(self, hetzner_client): return BoundFloatingIP(client=hetzner_client.floating_ips, data=dict(id=14)) def test_bound_floating_ip_init(self, floating_ip_response): bound_floating_ip = BoundFloatingIP( client=mock.MagicMock(), data=floating_ip_response['floating_ip']) assert bound_floating_ip.id == 4711 assert bound_floating_ip.description == "Web Frontend" assert bound_floating_ip.name == "Web Frontend" assert bound_floating_ip.ip == "131.232.99.1" assert bound_floating_ip.type == "ipv4" assert bound_floating_ip.protection == {"delete": False} assert bound_floating_ip.labels == {} assert bound_floating_ip.blocked is False assert isinstance(bound_floating_ip.server, BoundServer) assert bound_floating_ip.server.id == 42 assert isinstance(bound_floating_ip.home_location, BoundLocation) assert bound_floating_ip.home_location.id == 1 assert bound_floating_ip.home_location.name == "fsn1" assert bound_floating_ip.home_location.description == "Falkenstein DC Park 1" assert bound_floating_ip.home_location.country == "DE" assert bound_floating_ip.home_location.city == "Falkenstein" assert bound_floating_ip.home_location.latitude == 50.47612 assert bound_floating_ip.home_location.longitude == 12.370071 def test_get_actions(self, hetzner_client, bound_floating_ip, response_get_actions): hetzner_client.request.return_value = response_get_actions actions = bound_floating_ip.get_actions(sort="id") hetzner_client.request.assert_called_with( url="/floating_ips/14/actions", method="GET", params={ "sort": "id", 'page': 1, 'per_page': 50 }) assert len(actions) == 1 assert isinstance(actions[0], BoundAction) assert actions[0].id == 13 assert actions[0].command == "assign_floating_ip" def test_update(self, hetzner_client, bound_floating_ip, response_update_floating_ip): hetzner_client.request.return_value = response_update_floating_ip floating_ip = bound_floating_ip.update(description="New description", name="New name") hetzner_client.request.assert_called_with(url="/floating_ips/14", method="PUT", json={ "description": "New description", "name": "New name" }) assert floating_ip.id == 4711 assert floating_ip.description == "New description" assert floating_ip.name == "New name" def test_delete(self, hetzner_client, bound_floating_ip, generic_action): hetzner_client.request.return_value = generic_action delete_success = bound_floating_ip.delete() hetzner_client.request.assert_called_with(url="/floating_ips/14", method="DELETE") assert delete_success is True def test_change_protection(self, hetzner_client, bound_floating_ip, generic_action): hetzner_client.request.return_value = generic_action action = bound_floating_ip.change_protection(True) hetzner_client.request.assert_called_with( url="/floating_ips/14/actions/change_protection", method="POST", json={"delete": True}) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "server", (Server(id=1), BoundServer(mock.MagicMock(), dict(id=1)))) def test_assign(self, hetzner_client, bound_floating_ip, server, generic_action): hetzner_client.request.return_value = generic_action action = bound_floating_ip.assign(server) hetzner_client.request.assert_called_with( url="/floating_ips/14/actions/assign", method="POST", json={"server": 1}) assert action.id == 1 assert action.progress == 0 def test_unassign(self, hetzner_client, bound_floating_ip, generic_action): hetzner_client.request.return_value = generic_action action = bound_floating_ip.unassign() hetzner_client.request.assert_called_with( url="/floating_ips/14/actions/unassign", method="POST") assert action.id == 1 assert action.progress == 0 def test_change_dns_ptr(self, hetzner_client, bound_floating_ip, generic_action): hetzner_client.request.return_value = generic_action action = bound_floating_ip.change_dns_ptr("1.2.3.4", "server02.example.com") hetzner_client.request.assert_called_with( url="/floating_ips/14/actions/change_dns_ptr", method="POST", json={ "ip": "1.2.3.4", "dns_ptr": "server02.example.com" }) assert action.id == 1 assert action.progress == 0
class TestFloatingIPsClient(object): @pytest.fixture() def floating_ips_client(self): return FloatingIPsClient(client=mock.MagicMock()) def test_get_by_id(self, floating_ips_client, floating_ip_response): floating_ips_client._client.request.return_value = floating_ip_response bound_floating_ip = floating_ips_client.get_by_id(1) floating_ips_client._client.request.assert_called_with( url="/floating_ips/1", method="GET") assert bound_floating_ip._client is floating_ips_client assert bound_floating_ip.id == 4711 assert bound_floating_ip.description == "Web Frontend" def test_get_by_name(self, floating_ips_client, one_floating_ips_response): floating_ips_client._client.request.return_value = one_floating_ips_response bound_floating_ip = floating_ips_client.get_by_name("Web Frontend") floating_ips_client._client.request.assert_called_with( url="/floating_ips", method="GET", params={"name": "Web Frontend"}) assert bound_floating_ip._client is floating_ips_client assert bound_floating_ip.id == 4711 assert bound_floating_ip.name == "Web Frontend" assert bound_floating_ip.description == "Web Frontend" @pytest.mark.parametrize("params", [{ 'label_selector': "label1", 'page': 1, 'per_page': 10 }, { 'name': "" }, {}]) def test_get_list(self, floating_ips_client, two_floating_ips_response, params): floating_ips_client._client.request.return_value = two_floating_ips_response result = floating_ips_client.get_list(**params) floating_ips_client._client.request.assert_called_with( url="/floating_ips", method="GET", params=params) bound_floating_ips = result.floating_ips assert result.meta is None assert len(bound_floating_ips) == 2 bound_floating_ip1 = bound_floating_ips[0] bound_floating_ip2 = bound_floating_ips[1] assert bound_floating_ip1._client is floating_ips_client assert bound_floating_ip1.id == 4711 assert bound_floating_ip1.description == "Web Frontend" assert bound_floating_ip2._client is floating_ips_client assert bound_floating_ip2.id == 4712 assert bound_floating_ip2.description == "Web Backend" @pytest.mark.parametrize("params", [{'label_selector': "label1"}, {}]) def test_get_all(self, floating_ips_client, two_floating_ips_response, params): floating_ips_client._client.request.return_value = two_floating_ips_response bound_floating_ips = floating_ips_client.get_all(**params) params.update({'page': 1, 'per_page': 50}) floating_ips_client._client.request.assert_called_with( url="/floating_ips", method="GET", params=params) assert len(bound_floating_ips) == 2 bound_floating_ip1 = bound_floating_ips[0] bound_floating_ip2 = bound_floating_ips[1] assert bound_floating_ip1._client is floating_ips_client assert bound_floating_ip1.id == 4711 assert bound_floating_ip1.description == "Web Frontend" assert bound_floating_ip2._client is floating_ips_client assert bound_floating_ip2.id == 4712 assert bound_floating_ip2.description == "Web Backend" def test_create_with_location(self, floating_ips_client, floating_ip_response): floating_ips_client._client.request.return_value = floating_ip_response response = floating_ips_client.create( "ipv6", "Web Frontend", home_location=Location(name="location"), ) floating_ips_client._client.request.assert_called_with( url="/floating_ips", method="POST", json={ 'description': "Web Frontend", 'type': "ipv6", 'home_location': "location" }) bound_floating_ip = response.floating_ip action = response.action assert bound_floating_ip._client is floating_ips_client assert bound_floating_ip.id == 4711 assert bound_floating_ip.description == "Web Frontend" assert action is None @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_create_with_server(self, floating_ips_client, server, floating_ip_create_response): floating_ips_client._client.request.return_value = floating_ip_create_response response = floating_ips_client.create(type="ipv6", description="Web Frontend", server=server) floating_ips_client._client.request.assert_called_with( url="/floating_ips", method="POST", json={ 'description': "Web Frontend", 'type': "ipv6", 'server': 1 }) bound_floating_ip = response.floating_ip action = response.action assert bound_floating_ip._client is floating_ips_client assert bound_floating_ip.id == 4711 assert bound_floating_ip.description == "Web Frontend" assert action.id == 13 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_create_with_name(self, floating_ips_client, server, floating_ip_create_response): floating_ips_client._client.request.return_value = floating_ip_create_response response = floating_ips_client.create(type="ipv6", description="Web Frontend", name="Web Frontend") floating_ips_client._client.request.assert_called_with( url="/floating_ips", method="POST", json={ 'description': "Web Frontend", 'type': "ipv6", 'name': "Web Frontend" }) bound_floating_ip = response.floating_ip action = response.action assert bound_floating_ip._client is floating_ips_client assert bound_floating_ip.id == 4711 assert bound_floating_ip.description == "Web Frontend" assert bound_floating_ip.name == "Web Frontend" assert action.id == 13 @pytest.mark.parametrize( "floating_ip", [FloatingIP(id=1), BoundFloatingIP(mock.MagicMock(), dict(id=1))]) def test_get_actions(self, floating_ips_client, floating_ip, response_get_actions): floating_ips_client._client.request.return_value = response_get_actions actions = floating_ips_client.get_actions(floating_ip) floating_ips_client._client.request.assert_called_with( url="/floating_ips/1/actions", method="GET", params={ 'page': 1, 'per_page': 50 }) assert len(actions) == 1 assert isinstance(actions[0], BoundAction) assert actions[0]._client == floating_ips_client._client.actions assert actions[0].id == 13 assert actions[0].command == "assign_floating_ip" @pytest.mark.parametrize( "floating_ip", [FloatingIP(id=1), BoundFloatingIP(mock.MagicMock(), dict(id=1))]) def test_update(self, floating_ips_client, floating_ip, response_update_floating_ip): floating_ips_client._client.request.return_value = response_update_floating_ip floating_ip = floating_ips_client.update(floating_ip, description="New description", name="New name") floating_ips_client._client.request.assert_called_with( url="/floating_ips/1", method="PUT", json={ "description": "New description", "name": "New name" }) assert floating_ip.id == 4711 assert floating_ip.description == "New description" assert floating_ip.name == "New name" @pytest.mark.parametrize( "floating_ip", [FloatingIP(id=1), BoundFloatingIP(mock.MagicMock(), dict(id=1))]) def test_change_protection(self, floating_ips_client, floating_ip, generic_action): floating_ips_client._client.request.return_value = generic_action action = floating_ips_client.change_protection(floating_ip, True) floating_ips_client._client.request.assert_called_with( url="/floating_ips/1/actions/change_protection", method="POST", json={"delete": True}) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "floating_ip", [FloatingIP(id=1), BoundFloatingIP(mock.MagicMock(), dict(id=1))]) def test_delete(self, floating_ips_client, floating_ip, generic_action): floating_ips_client._client.request.return_value = generic_action delete_success = floating_ips_client.delete(floating_ip) floating_ips_client._client.request.assert_called_with( url="/floating_ips/1", method="DELETE") assert delete_success is True @pytest.mark.parametrize("server,floating_ip", [(Server(id=1), FloatingIP(id=12)), (BoundServer(mock.MagicMock(), dict(id=1)), BoundFloatingIP(mock.MagicMock(), dict(id=12)))] ) def test_assign(self, floating_ips_client, server, floating_ip, generic_action): floating_ips_client._client.request.return_value = generic_action action = floating_ips_client.assign(floating_ip, server) floating_ips_client._client.request.assert_called_with( url="/floating_ips/12/actions/assign", method="POST", json={"server": 1}) assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "floating_ip", [FloatingIP(id=12), BoundFloatingIP(mock.MagicMock(), dict(id=12))]) def test_unassign(self, floating_ips_client, floating_ip, generic_action): floating_ips_client._client.request.return_value = generic_action action = floating_ips_client.unassign(floating_ip) floating_ips_client._client.request.assert_called_with( url="/floating_ips/12/actions/unassign", method="POST") assert action.id == 1 assert action.progress == 0 @pytest.mark.parametrize( "floating_ip", [FloatingIP(id=12), BoundFloatingIP(mock.MagicMock(), dict(id=12))]) def test_change_dns_ptr(self, floating_ips_client, floating_ip, generic_action): floating_ips_client._client.request.return_value = generic_action action = floating_ips_client.change_dns_ptr(floating_ip, "1.2.3.4", "server02.example.com") floating_ips_client._client.request.assert_called_with( url="/floating_ips/12/actions/change_dns_ptr", method="POST", json={ "ip": "1.2.3.4", "dns_ptr": "server02.example.com" }) assert action.id == 1 assert action.progress == 0
def _handle_changed_server_networks(self, defn: HetznerCloudDefinition, allow_recreate: bool) -> None: """ Detects and corrects any virtual network state desynchronisation. """ attached: Set[str] = { x.network.id for x in self.get_instance().private_net } # Detach server from networks for name in self.server_networks.keys(): nw: Optional[BoundNetwork] = self.get_client( ).networks.get_by_name(name) # Detect destroyed networks if nw is None: if name not in defn.server_networks: # we dont need it self.logger.warn( f"forgetting about network ‘{name}’ that no longer exists" " and is no longer needed by the deployment specification" ) self._update_attr("server_networks", name, None) else: # we do need it raise Exception( f"network ‘{name}’ (used by {self.full_name}) no longer exists;" " run ‘nixops deploy --check’ to update resource state" ) # Detect network detachment elif nw.id not in attached: self.logger.warn( f"instance was manually detached from network ‘{name}’ [{nw.id}]" ) if name in defn.server_networks: self._update_attr("server_networks", name, None) # Detach from existing networks if required. elif name not in defn.server_networks: self.logger.log(f"detaching from network ‘{name}’ [{nw.id}]") self.get_client().servers.detach_from_network( server=Server(self.vm_id), network=nw).wait_until_finished() self._update_attr("server_networks", name, None) # Attach server to networks for name, x in defn.server_networks.items(): if name not in self.server_networks: nw = self.get_client().networks.get_by_name(name) if nw is None: raise Exception( f"tried to attach instance to network ‘{name}’" " but it doesn't exist...") # NixOps will update machines in parallel, so retry # network attachment to deal with resource conflict. def attach_to_network() -> bool: try: self.wait_on_action( self.get_client().servers.attach_to_network( server=Server(self.vm_id), network=nw, ip=x["privateIpAddress"], alias_ips=x["aliasIpAddresses"], )) except APIException as e: if e.code == "conflict": return False else: raise else: self._update_attr("server_networks", x["network"], x) return True self.logger.log( f"attaching instance to network ‘{name}’ [{nw.id}]...") check_wait(attach_to_network)
def test_remove_target(self, hetzner_client, load_balancer): action = hetzner_client.load_balancers.remove_target(load_balancer, LoadBalancerTarget(type="server", server=Server(id=1))) assert action.id == 13 assert action.command == "remove_target"
class TestVolumesClient(object): def test_get_by_id(self, hetzner_client): bound_volume = hetzner_client.volumes.get_by_id(4711) assert bound_volume.id == 4711 assert bound_volume.name == "database-storage" assert bound_volume.size == 42 def test_get_by_name(self, hetzner_client): bound_volume = hetzner_client.volumes.get_by_name("database-storage") assert bound_volume.id == 4711 assert bound_volume.name == "database-storage" assert bound_volume.size == 42 def test_get_list(self, hetzner_client): result = hetzner_client.volumes.get_list() bound_volumes = result.volumes assert bound_volumes[0].id == 4711 assert bound_volumes[0].name == "database-storage" assert bound_volumes[0].size == 42 @pytest.mark.parametrize( "server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]) def test_create(self, hetzner_client, server): response = hetzner_client.volumes.create( 42, "test-database", location=Location(name="nbg1"), automount=False, format="xfs") volume = response.volume action = response.action next_actions = response.next_actions assert volume.id == 4711 assert volume.name == "database-storage" assert volume.size == 42 assert action.id == 13 assert action.command == "create_volume" assert len(next_actions) == 1 assert next_actions[0].id == 13 assert next_actions[0].command == "start_server" @pytest.mark.parametrize( "volume", [Volume(id=1), BoundVolume(mock.MagicMock(), dict(id=1))]) def test_get_actions(self, hetzner_client, volume): actions = hetzner_client.volumes.get_actions(volume) assert len(actions) == 1 assert actions[0].id == 13 assert actions[0].command == "attach_volume" @pytest.mark.parametrize( "volume", [Volume(id=1), BoundVolume(mock.MagicMock(), dict(id=1))]) def test_update(self, hetzner_client, volume): volume = hetzner_client.volumes.update(volume, name="new-name", labels={}) assert volume.id == 4711 assert volume.name == "new-name" @pytest.mark.parametrize( "volume", [Volume(id=1), BoundVolume(mock.MagicMock(), dict(id=1))]) def test_delete(self, hetzner_client, volume): delete_success = hetzner_client.volumes.delete(volume) assert delete_success is True @pytest.mark.parametrize("server,volume", [(Server(id=43), Volume(id=4711)), (BoundServer(mock.MagicMock(), dict(id=43)), BoundVolume(mock.MagicMock(), dict(id=4711)))]) def test_attach(self, hetzner_client, server, volume): action = hetzner_client.volumes.attach(volume, server) assert action.id == 13 assert action.progress == 0 assert action.command == "attach_volume" @pytest.mark.parametrize( "volume", [Volume(id=4711), BoundVolume(mock.MagicMock(), dict(id=4711))]) def test_detach(self, hetzner_client, volume): action = hetzner_client.volumes.detach(volume) assert action.id == 13 assert action.progress == 0 assert action.command == "detach_volume" @pytest.mark.parametrize( "volume", [Volume(id=4711), BoundVolume(mock.MagicMock(), dict(id=4711))]) def test_resize(self, hetzner_client, volume): action = hetzner_client.volumes.resize(volume, 50) assert action.id == 13 assert action.progress == 0 assert action.command == "resize_volume"