def _create_server(self): self.module.fail_on_missing_params( required_params=["name", "server_type", "image"]) params = { "name": self.module.params.get("name"), "server_type": self.client.server_types.get_by_name( self.module.params.get("server_type")), "user_data": self.module.params.get("user_data"), "labels": self.module.params.get("labels"), } if self.client.images.get_by_name( self.module.params.get("image")) is not None: # When image name is not available look for id instead params["image"] = self.client.images.get_by_name( self.module.params.get("image")) else: params["image"] = self.client.images.get_by_id( self.module.params.get("image")) if self.module.params.get("ssh_keys") is not None: params["ssh_keys"] = [ SSHKey(name=ssh_key_name) for ssh_key_name in self.module.params.get("ssh_keys") ] if self.module.params.get("volumes") is not None: params["volumes"] = [ Volume(id=volume_id) for volume_id in self.module.params.get("volumes") ] if self.module.params.get( "location") is None and self.module.params.get( "datacenter") is None: # When not given, the API will choose the location. params["location"] = None params["datacenter"] = None elif self.module.params.get( "location" ) is not None and self.module.params.get("datacenter") is None: params["location"] = self.client.locations.get_by_name( self.module.params.get("location")) elif self.module.params.get( "location") is None and self.module.params.get( "datacenter") is not None: params["datacenter"] = self.client.datacenters.get_by_name( self.module.params.get("datacenter")) if not self.module.check_mode: resp = self.client.servers.create(**params) self.result["root_password"] = resp.root_password resp.action.wait_until_finished(max_retries=1000) [action.wait_until_finished() for action in resp.next_actions] self._mark_as_changed() self._get_server()
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"
def realise_resize_volume(self, allow_recreate: bool) -> None: defn: VolumeOptions = self.get_defn().config size: int = self._state["size"] if size > defn.size: raise Exception("decreasing a volume's size isn't supported.") elif size < defn.size: self.logger.log( f"increasing volume size from {size} GiB to {defn.size} GiB") self.get_client().volumes.resize( volume=Volume(self.resource_id), size=defn.size).wait_until_finished() with self.depl._db: self._state["size"] = defn.size self.needsFSResize = True
async def stop(self): if self.running == True: print("Stopping " + self.name) self.client.servers.shutdown(self.server) time.sleep(30) while self.server.status != Server.STATUS_OFF: print(self.server.status) time.sleep(5) serv = self.client.servers.get_by_id(self.server.id) self.server = serv print("Server is now stopped") time.sleep(1) self.client.volumes.detach(Volume(self.volume)) time.sleep(1) self.client.servers.delete(self.server) self.running = False self.server = None else: print(self.name + " isn't running")
async def start(self): if self.running == False: print("Starting " + self.name) response = self.client.servers.create( self.name, server_type=ServerType(name=self.servertype), image=Image(self.snapshot), location=Location(self.location), volumes=[Volume(self.volume)]) self.server = response.server #while self.server.status != Server.STATUS_RUNNING: # print(self.server.status) # time.sleep(2) # serv = self.client.servers.get_by_id(self.server.id) # self.server = serv #print("Server is now running") self.running = True else: print(self.name + " is already running")
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
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 create(self, defn: HcloudDefinition, check, allow_reboot, allow_recreate): assert isinstance(defn, HcloudDefinition) hetzner = defn.config.hcloud self.token = get_access_token(hetzner) if self.state not in (MachineState.RESCUE, MachineState.UP) or check: self.check() self.set_common_state(defn) self.upgrade_disk = hetzner.upgradeDisk # TODO maybe bootstrap can be automated with vncdotool image_id = self._fetch_image_id(hetzner.image, hetzner.image_selector) if self.image_id is None: self.image_id = image_id elif self.image_id != image_id: self.warn( f"image_id changed from {self.image_id} to {image_id} but can't update image of a VM." ) if self.location is None: self.location = hetzner.location elif self.location != hetzner.location: self.warn( f"location changed from {self.location} to {hetzner.location} but can't update location of a VM." ) if self.vm_id is not None and hetzner.serverType != self.server_type: # TODO Check if server can be upgraded before hitting the Hetzner API # https://docs.hetzner.cloud/#server-actions-change-the-type-of-a-server do_upgrade = True # Only confirm if upgrade_disk is True because then the upgrade can't be undone if self.upgrade_disk: do_upgrade = self.depl.logger.confirm( f"are you sure you want to change Hetzner server {self.name} type from " + f"{self.server_type} to {hetzner.serverType}?") if do_upgrade: self.log_start("Changing Hetzner server type...") self._server.shutdown().wait_until_finished() self.wait_for_down(callback=lambda: self.log_continue(".")) self._server.change_type( ServerType(name=hetzner.serverType), upgrade_disk=self.upgrade_disk).wait_until_finished() self._server.power_on() self.wait_for_up(callback=lambda: self.log_continue(".")) self.log_end("") self.server_type = hetzner.serverType ssh_keys = [ k.name if isinstance(k, ResourceEval) else k for k in hetzner.sshKeys ] if self.state != MachineState.MISSING and ssh_keys != self.ssh_keys: self.logger.warn( f"SSH keys cannot be changed after the server is created.") volume_ids = [] filesystems = {} for volumeopts in hetzner.volumes: volume = volumeopts.volume if isinstance(volume, str): volume_model = self._client.volumes.get_by_name(volume) volume_name = volume volume_id = volume_model.id volume_loc = volume_model.location.name else: volume_res = self.depl.get_typed_resource( volume._name, "hcloud-volume", HcloudVolumeState) volume_name = volume_res.name volume_id = volume_res.hcloud_id assert volume_id is not None volume_loc = volume_res.location if volume_loc != self.location: raise Exception( f"Volume {volume_name!r} is in a different location from server {self.name!r}" ) volume_ids.append(volume_id) if volumeopts.mountPoint is not None: fs = dict(volumeopts.fileSystem) fs["device"] = f"/dev/disk/by-id/scsi-0HC_Volume_{volume_id}" filesystems[volumeopts.mountPoint] = fs has_priv = self._ssh_private_key is not None has_pub = self._ssh_public_key is not None assert has_priv == has_pub if not has_priv: self.log("Generating SSH keypair...") (self._ssh_private_key, self._ssh_public_key) = create_key_pair() if self.vm_id: if self.volume_ids != volume_ids: current = set(self.volume_ids) new = set(volume_ids) volumes_client = self._client.volumes self.log_start("Updating volumes...") for v in current - new: volumes_client.detach(Volume(id=v)) self.log_continue(".") for v in new - current: volumes_client.attach( Volume(id=v), self._server, automount=False).wait_until_finished() self.log_continue(".") self.log_end("") self.volume_ids = volume_ids else: self.log_start( "Creating Hetzner Cloud VM (" + f"image '{image_id}', type '{hetzner.serverType}', location '{hetzner.location}'" + ")...") response = self._client.servers.create( name=self.name, ssh_keys=[SSHKey(name=k) for k in ssh_keys], volumes=[Volume(id=v) for v in volume_ids], server_type=ServerType(self.server_type), image=Image(id=self.image_id), # Set labels so we can find the instance if nixops crashes before writing vm_id labels=dict(self._server_labels()), user_data=None if self._ssh_public_key is None else yaml.dump( {"public-keys": [self._ssh_public_key]}), ) self.log_end("") self.public_ipv4 = response.server.public_net.ipv4.ip self.log_start("waiting for SSH...") self.wait_for_up(callback=lambda: self.log_continue(".")) self.log_end("") with self.depl._db: self.vm_id = response.server.id # TODO get state from creation response self.state = MachineState.STARTING self.ssh_keys = ssh_keys self.volume_ids = volume_ids self._detect_hardware() self._update_host_keys() self.filesystems = filesystems
def test_created_is_datetime(self): server = Volume(id=1, created="2016-01-30T23:50+00:00") assert server.created == datetime.datetime(2016, 1, 30, 23, 50, tzinfo=tzoffset(None, 0))
def _create_server(self): self.module.fail_on_missing_params( required_params=["name", "server_type", "image"] ) params = { "name": self.module.params.get("name"), "server_type": self.client.server_types.get_by_name( self.module.params.get("server_type") ), "user_data": self.module.params.get("user_data"), "labels": self.module.params.get("labels"), } image = self.client.images.get_by_name(self.module.params.get("image")) if image is not None: # When image name is not available look for id instead params["image"] = image else: params["image"] = self.client.images.get_by_id(self.module.params.get("image")) if self.module.params.get("ssh_keys") is not None: params["ssh_keys"] = [ SSHKey(name=ssh_key_name) for ssh_key_name in self.module.params.get("ssh_keys") ] if self.module.params.get("volumes") is not None: params["volumes"] = [ Volume(id=volume_id) for volume_id in self.module.params.get("volumes") ] if self.module.params.get("firewalls") is not None: params["firewalls"] = [] for fw in self.module.params.get("firewalls"): f = self.client.firewalls.get_by_name(fw) if f is not None: # When firewall name is not available look for id instead params["firewalls"].append(f) else: params["firewalls"].append(self.client.firewalls.get_by_id(fw)) if self.module.params.get("location") is None and self.module.params.get("datacenter") is None: # When not given, the API will choose the location. params["location"] = None params["datacenter"] = None elif self.module.params.get("location") is not None and self.module.params.get("datacenter") is None: params["location"] = self.client.locations.get_by_name( self.module.params.get("location") ) elif self.module.params.get("location") is None and self.module.params.get("datacenter") is not None: params["datacenter"] = self.client.datacenters.get_by_name( self.module.params.get("datacenter") ) if not self.module.check_mode: try: resp = self.client.servers.create(**params) self.result["root_password"] = resp.root_password resp.action.wait_until_finished(max_retries=1000) [action.wait_until_finished() for action in resp.next_actions] rescue_mode = self.module.params.get("rescue_mode") if rescue_mode: self._get_server() self._set_rescue_mode(rescue_mode) backups = self.module.params.get("backups") if backups: self._get_server() self.hcloud_server.enable_backup().wait_until_finished() delete_protection = self.module.params.get("delete_protection") rebuild_protection = self.module.params.get("rebuild_protection") if delete_protection is not None and rebuild_protection is not None: self._get_server() self.hcloud_server.change_protection(delete=delete_protection, rebuild=rebuild_protection).wait_until_finished() except Exception as e: self.module.fail_json(msg=e.message) self._mark_as_changed() self._get_server()
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 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"