def update(self, defn: HcloudVolumeDefinition, model: BoundVolume) -> None: if defn.config.location != model.location.name: self.logger.error( "Cannot update the location of a Hetzner Cloud volume") if defn.config.size < model.size: self.logger.error("Cannot shrink volume") elif defn.config.size > model.size: if not self.depl.logger.confirm(f"Resize volume {self.name!r}?"): return model.resize(defn.config.size).wait_until_finished() self.size = defn.config.size
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 __init__(self, client, data, complete=True): datacenter = data.get('datacenter') if datacenter is not None: data['datacenter'] = BoundDatacenter(client._client.datacenters, datacenter) volumes = data.get('volumes', []) if volumes: volumes = [BoundVolume(client._client.volumes, {"id": volume}, complete=False) for volume in volumes] data['volumes'] = volumes image = data.get("image", None) if image is not None: data['image'] = BoundImage(client._client.images, image) iso = data.get("iso", None) if iso is not None: data['iso'] = BoundIso(client._client.isos, iso) server_type = data.get("server_type") if server_type is not None: data['server_type'] = BoundServerType(client._client.server_types, server_type) public_net = data.get("public_net") if public_net: ipv4_address = IPv4Address(**public_net['ipv4']) ipv6_network = IPv6Network(**public_net['ipv6']) floating_ips = [BoundFloatingIP(client._client.floating_ips, {"id": floating_ip}, complete=False) for floating_ip in public_net['floating_ips']] data['public_net'] = PublicNetwork(ipv4=ipv4_address, ipv6=ipv6_network, floating_ips=floating_ips) super(BoundServer, self).__init__(client, data, complete)
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 bound_volume(self, hetzner_client): return BoundVolume(client=hetzner_client.volumes, data=dict(id=14))
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
def __init__(self, client, data, complete=True): datacenter = data.get('datacenter') if datacenter is not None: data['datacenter'] = BoundDatacenter(client._client.datacenters, datacenter) volumes = data.get('volumes', []) if volumes: volumes = [ BoundVolume(client._client.volumes, {"id": volume}, complete=False) for volume in volumes ] data['volumes'] = volumes image = data.get("image", None) if image is not None: data['image'] = BoundImage(client._client.images, image) iso = data.get("iso", None) if iso is not None: data['iso'] = BoundIso(client._client.isos, iso) server_type = data.get("server_type") if server_type is not None: data['server_type'] = BoundServerType(client._client.server_types, server_type) public_net = data.get("public_net") if public_net: ipv4_address = IPv4Address(**public_net['ipv4']) ipv6_network = IPv6Network(**public_net['ipv6']) floating_ips = [ BoundFloatingIP(client._client.floating_ips, {"id": floating_ip}, complete=False) for floating_ip in public_net['floating_ips'] ] firewalls = [ PublicNetworkFirewall(BoundFirewall(client._client.firewalls, {"id": firewall["id"]}, complete=False), status=firewall["status"]) for firewall in public_net.get("firewalls", []) ] data['public_net'] = PublicNetwork(ipv4=ipv4_address, ipv6=ipv6_network, floating_ips=floating_ips, firewalls=firewalls) private_nets = data.get("private_net") if private_nets: private_nets = [ PrivateNet(network=BoundNetwork(client._client.networks, {"id": private_net['network']}, complete=False), ip=private_net['ip'], alias_ips=private_net['alias_ips'], mac_address=private_net['mac_address']) for private_net in private_nets ] data['private_net'] = private_nets super(BoundServer, self).__init__(client, data, complete)
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"