diff --git a/openstack/compute/v2/_proxy.py b/openstack/compute/v2/_proxy.py index c44362dbe..9aeb983f4 100644 --- a/openstack/compute/v2/_proxy.py +++ b/openstack/compute/v2/_proxy.py @@ -568,6 +568,211 @@ class Proxy(proxy2.BaseProxy): security_group_id = resource2.Resource._get_id(security_group) server.remove_security_group(self.session, security_group_id) + def add_fixed_ip_to_server(self, server, network_id): + """Adds a fixed IP address to a server instance. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :param network_id: The ID of the network from which a fixed IP address + is about to be allocated. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.add_fixed_ip(self.session, network_id) + + def remove_fixed_ip_from_server(self, server, address): + """Removes a fixed IP address from a server instance. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :param address: The fixed IP address to be disassociated from the + server. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.remove_fixed_ip(self.session, address) + + def add_floating_ip_to_server(self, server, address, fixed_address=None): + """Adds a floating IP address to a server instance. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :param address: The floating IP address to be added to the server. + :param fixed_address: The fixed IP address to be associated with the + floating IP address. Used when the server is + connected to multiple networks. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.add_floating_ip(self.session, address, + fixed_address=fixed_address) + + def remove_floating_ip_from_server(self, server, address): + """Removes a floating IP address from a server instance. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :param address: The floating IP address to be disassociated from the + server. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.remove_floating_ip(self.session, address) + + def pause_server(self, server): + """Pauses a server and changes its status to ``PAUSED``. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.pause(self.session) + + def unpause_server(self, server): + """Unpauses a paused server and changes its status to ``ACTIVE``. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.unpause(self.session) + + def suspend_server(self, server): + """Suspends a server and changes its status to ``SUSPENDED``. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.suspend(self.session) + + def resume_server(self, server): + """Resumes a suspended server and changes its status to ``ACTIVE``. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.resume(self.session) + + def lock_server(self, server): + """Locks a server. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.lock(self.session) + + def unlock_server(self, server): + """Unlocks a locked server. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.unlock(self.session) + + def rescue_server(self, server, admin_pass=None, image_ref=None): + """Puts a server in rescue mode and changes it status to ``RESCUE``. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :param admin_pass: The password for the rescued server. If you omit + this parameter, the operation generates a new + password. + :param image_ref: The image reference to use to rescue your server. + This can be the image ID or its full URL. If you + omit this parameter, the base image reference will + be used. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.rescue(self.session, admin_pass=admin_pass, image_ref=image_ref) + + def unrescue_server(self, server): + """Unrescues a server and changes its status to ``ACTIVE``. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.unrescue(self.session) + + def evacuate_server(self, server, host=None, admin_pass=None, force=None): + """Evacuates a server from a failed host to a new host. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :param host: An optional parameter specifying the name or ID of the + host to which the server is evacuated. + :param admin_pass: An optional parameter specifying the administrative + password to access the evacuated or rebuilt server. + :param force: Force an evacuation by not verifying the provided + destination host by the scheduler. (New in API version + 2.29). + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.evacuate(self.session, host=host, admin_pass=admin_pass, + force=force) + + def start_server(self, server): + """Starts a stopped server and changes its state to ``ACTIVE``. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.start(self.session) + + def stop_server(self, server): + """Stops a running server and changes its state to ``SHUTOFF``. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.stop(self.session) + + def shelve_server(self, server): + """Shelves a server. + + All associated data and resources are kept but anything still in + memory is not retained. Policy defaults enable only users with + administrative role or the owner of the server to perform this + operation. Cloud provides could change this permission though. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.shelve(self.session) + + def unshelve_server(self, server): + """Unselves or restores a shelved server. + + Policy defaults enable only users with administrative role or the + owner of the server to perform this operation. Cloud provides could + change this permission though. + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.unshelve(self.session) + def wait_for_server(self, server, status='ACTIVE', failures=['ERROR'], interval=2, wait=120): return resource2.wait_for_status(self.session, server, status, diff --git a/openstack/compute/v2/server.py b/openstack/compute/v2/server.py index 07d6f1ba7..4dd3bbb01 100644 --- a/openstack/compute/v2/server.py +++ b/openstack/compute/v2/server.py @@ -209,6 +209,86 @@ class Server(resource2.Resource, metadata.MetadataMixin): body = {"os-resetState": {"state": state}} self._action(session, body) + def add_fixed_ip(self, session, network_id): + body = {"addFixedIp": {"networkId": network_id}} + self._action(session, body) + + def remove_fixed_ip(self, session, address): + body = {"removeFixedIp": {"address": address}} + self._action(session, body) + + def add_floating_ip(self, session, address, fixed_address=None): + body = {"addFloatingIp": {"address": address}} + if fixed_address is not None: + body['addFloatingIp']['fixed_address'] = fixed_address + self._action(session, body) + + def remove_floating_ip(self, session, address): + body = {"removeFloatingIp": {"address": address}} + self._action(session, body) + + def pause(self, session): + body = {"pause": None} + self._action(session, body) + + def unpause(self, session): + body = {"unpause": None} + self._action(session, body) + + def suspend(self, session): + body = {"suspend": None} + self._action(session, body) + + def resume(self, session): + body = {"resume": None} + self._action(session, body) + + def lock(self, session): + body = {"lock": None} + self._action(session, body) + + def unlock(self, session): + body = {"unlock": None} + self._action(session, body) + + def rescue(self, session, admin_pass=None, image_ref=None): + body = {"rescue": {}} + if admin_pass is not None: + body["rescue"]["adminPass"] = admin_pass + if image_ref is not None: + body["rescue"]["rescue_image_ref"] = image_ref + self._action(session, body) + + def unrescue(self, session): + body = {"unrescue": None} + self._action(session, body) + + def evacuate(self, session, host=None, admin_pass=None, force=None): + body = {"evacuate": {}} + if host is not None: + body["evacuate"]["host"] = host + if admin_pass is not None: + body["evacuate"]["adminPass"] = admin_pass + if force is not None: + body["evacuate"]["force"] = force + self._action(session, body) + + def start(self, session): + body = {"os-start": None} + self._action(session, body) + + def stop(self, session): + body = {"os-stop": None} + self._action(session, body) + + def shelve(self, session): + body = {"shelve": None} + self._action(session, body) + + def unshelve(self, session): + body = {"unshelve": None} + self._action(session, body) + class ServerDetail(Server): base_path = '/servers/detail' diff --git a/openstack/tests/unit/compute/v2/test_proxy.py b/openstack/tests/unit/compute/v2/test_proxy.py index afa66f998..f50112fcf 100644 --- a/openstack/tests/unit/compute/v2/test_proxy.py +++ b/openstack/tests/unit/compute/v2/test_proxy.py @@ -286,6 +286,121 @@ class TestComputeProxy(test_proxy_base2.TestProxyBase): expected_kwargs={"metadata": {"k1": "v1"}, "image": id}) + def test_add_fixed_ip_to_server(self): + self._verify("openstack.compute.v2.server.Server.add_fixed_ip", + self.proxy.add_fixed_ip_to_server, + method_args=["value", "network-id"], + expected_args=["network-id"]) + + def test_fixed_ip_from_server(self): + self._verify("openstack.compute.v2.server.Server.remove_fixed_ip", + self.proxy.remove_fixed_ip_from_server, + method_args=["value", "address"], + expected_args=["address"]) + + def test_floating_ip_to_server(self): + self._verify("openstack.compute.v2.server.Server.add_floating_ip", + self.proxy.add_floating_ip_to_server, + method_args=["value", "floating-ip"], + expected_args=["floating-ip"], + expected_kwargs={'fixed_address': None}) + + def test_add_floating_ip_to_server_with_fixed_addr(self): + self._verify("openstack.compute.v2.server.Server.add_floating_ip", + self.proxy.add_floating_ip_to_server, + method_args=["value", "floating-ip", 'fixed-addr'], + expected_args=["floating-ip"], + expected_kwargs={'fixed_address': 'fixed-addr'}) + + def test_remove_floating_ip_from_server(self): + self._verify("openstack.compute.v2.server.Server.remove_floating_ip", + self.proxy.remove_floating_ip_from_server, + method_args=["value", "address"], + expected_args=["address"]) + + def test_server_pause(self): + self._verify("openstack.compute.v2.server.Server.pause", + self.proxy.pause_server, + method_args=["value"]) + + def test_server_unpause(self): + self._verify("openstack.compute.v2.server.Server.unpause", + self.proxy.unpause_server, + method_args=["value"]) + + def test_server_suspend(self): + self._verify("openstack.compute.v2.server.Server.suspend", + self.proxy.suspend_server, + method_args=["value"]) + + def test_server_resume(self): + self._verify("openstack.compute.v2.server.Server.resume", + self.proxy.resume_server, + method_args=["value"]) + + def test_server_lock(self): + self._verify("openstack.compute.v2.server.Server.lock", + self.proxy.lock_server, + method_args=["value"]) + + def test_server_unlock(self): + self._verify("openstack.compute.v2.server.Server.unlock", + self.proxy.unlock_server, + method_args=["value"]) + + def test_server_rescue(self): + self._verify("openstack.compute.v2.server.Server.rescue", + self.proxy.rescue_server, + method_args=["value"], + expected_kwargs={"admin_pass": None, "image_ref": None}) + + def test_server_rescue_with_options(self): + self._verify("openstack.compute.v2.server.Server.rescue", + self.proxy.rescue_server, + method_args=["value", 'PASS', 'IMG'], + expected_kwargs={"admin_pass": 'PASS', + "image_ref": 'IMG'}) + + def test_server_unrescue(self): + self._verify("openstack.compute.v2.server.Server.unrescue", + self.proxy.unrescue_server, + method_args=["value"]) + + def test_server_evacuate(self): + self._verify("openstack.compute.v2.server.Server.evacuate", + self.proxy.evacuate_server, + method_args=["value"], + expected_kwargs={"host": None, "admin_pass": None, + "force": None}) + + def test_server_evacuate_with_options(self): + self._verify("openstack.compute.v2.server.Server.evacuate", + self.proxy.evacuate_server, + method_args=["value", 'HOST2', 'NEW_PASS', True], + expected_kwargs={"host": "HOST2", + "admin_pass": 'NEW_PASS', + "force": True}) + + def test_server_start(self): + self._verify("openstack.compute.v2.server.Server.start", + self.proxy.start_server, + method_args=["value"]) + + def test_server_stop(self): + self._verify("openstack.compute.v2.server.Server.stop", + self.proxy.stop_server, + method_args=["value"]) + + def test_server_shelve(self): + self._verify("openstack.compute.v2.server.Server.shelve", + self.proxy.shelve_server, + method_args=["value"]) + + def test_server_unshelve(self): + self._verify("openstack.compute.v2.server.Server.unshelve", + self.proxy.unshelve_server, + method_args=["value"]) + def test_availability_zones(self): self.verify_list_no_kwargs(self.proxy.availability_zones, az.AvailabilityZone, diff --git a/openstack/tests/unit/compute/v2/test_server.py b/openstack/tests/unit/compute/v2/test_server.py index 3e236328d..bb5eabf3e 100644 --- a/openstack/tests/unit/compute/v2/test_server.py +++ b/openstack/tests/unit/compute/v2/test_server.py @@ -320,3 +320,247 @@ class TestServer(testtools.TestCase): headers = {'Accept': ''} self.sess.post.assert_called_with( url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_add_fixed_ip(self): + sot = server.Server(**EXAMPLE) + + res = sot.add_fixed_ip(self.sess, "NETWORK-ID") + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"addFixedIp": {"networkId": "NETWORK-ID"}} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_remove_fixed_ip(self): + sot = server.Server(**EXAMPLE) + + res = sot.remove_fixed_ip(self.sess, "ADDRESS") + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"removeFixedIp": {"address": "ADDRESS"}} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_add_floating_ip(self): + sot = server.Server(**EXAMPLE) + + res = sot.add_floating_ip(self.sess, "FLOATING-IP") + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"addFloatingIp": {"address": "FLOATING-IP"}} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_add_floating_ip_with_fixed_addr(self): + sot = server.Server(**EXAMPLE) + + res = sot.add_floating_ip(self.sess, "FLOATING-IP", "FIXED-ADDR") + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"addFloatingIp": {"address": "FLOATING-IP", + "fixed_address": "FIXED-ADDR"}} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_remove_floating_ip(self): + sot = server.Server(**EXAMPLE) + + res = sot.remove_floating_ip(self.sess, "I-AM-FLOATING") + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"removeFloatingIp": {"address": "I-AM-FLOATING"}} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_pause(self): + sot = server.Server(**EXAMPLE) + + res = sot.pause(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"pause": None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_unpause(self): + sot = server.Server(**EXAMPLE) + + res = sot.unpause(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"unpause": None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_suspend(self): + sot = server.Server(**EXAMPLE) + + res = sot.suspend(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"suspend": None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_resume(self): + sot = server.Server(**EXAMPLE) + + res = sot.resume(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"resume": None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_lock(self): + sot = server.Server(**EXAMPLE) + + res = sot.lock(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"lock": None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_unlock(self): + sot = server.Server(**EXAMPLE) + + res = sot.unlock(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"unlock": None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_rescue(self): + sot = server.Server(**EXAMPLE) + + res = sot.rescue(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"rescue": {}} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_rescue_with_options(self): + sot = server.Server(**EXAMPLE) + + res = sot.rescue(self.sess, admin_pass='SECRET', image_ref='IMG-ID') + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"rescue": {'adminPass': 'SECRET', + 'rescue_image_ref': 'IMG-ID'}} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_unrescue(self): + sot = server.Server(**EXAMPLE) + + res = sot.unrescue(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"unrescue": None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_evacuate(self): + sot = server.Server(**EXAMPLE) + + res = sot.evacuate(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"evacuate": {}} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_evacuate_with_options(self): + sot = server.Server(**EXAMPLE) + + res = sot.evacuate(self.sess, host='HOST2', admin_pass='NEW_PASS', + force=True) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"evacuate": {'host': 'HOST2', 'adminPass': 'NEW_PASS', + 'force': True}} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_start(self): + sot = server.Server(**EXAMPLE) + + res = sot.start(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"os-start": None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_stop(self): + sot = server.Server(**EXAMPLE) + + res = sot.stop(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"os-stop": None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_shelve(self): + sot = server.Server(**EXAMPLE) + + res = sot.shelve(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"shelve": None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) + + def test_unshelve(self): + sot = server.Server(**EXAMPLE) + + res = sot.unshelve(self.sess) + + self.assertIsNone(res) + url = 'servers/IDENTIFIER/action' + body = {"unshelve": None} + headers = {'Accept': ''} + self.sess.post.assert_called_with( + url, endpoint_filter=sot.service, json=body, headers=headers) diff --git a/openstack/tests/unit/test_session.py b/openstack/tests/unit/test_session.py index a1f8ebc85..89e8f452d 100644 --- a/openstack/tests/unit/test_session.py +++ b/openstack/tests/unit/test_session.py @@ -278,7 +278,7 @@ class TestSession(testtools.TestCase): sot = session.Session(None) endpoint = session.Session._Endpoint(root, versions) rv = sot._get_version_match(endpoint, session.Version(2, 0), "service") - self.assertEqual(rv, root+match) + self.assertEqual(rv, root + match) def test__get_version_match_project_id(self): match = "http://devstack/v2" diff --git a/tox.ini b/tox.ini index 74b787108..ecb18c79c 100644 --- a/tox.ini +++ b/tox.ini @@ -44,5 +44,6 @@ commands = python setup.py test --coverage --coverage-package-name=openstack --t commands = python setup.py build_sphinx [flake8] +ignore=D100,D101,D102,D103,D104,D105,D200,D202,D204,D205,D211,D301,D400,D401 show-source = True exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build