diff --git a/.gitignore b/.gitignore index 43b3770d9..310b7d77f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ /.vagrant /build +/test/nosetests.xml diff --git a/nailgun/nailgun/api/handlers.py b/nailgun/nailgun/api/handlers.py index 9dd4d1cdd..ce2f15e95 100644 --- a/nailgun/nailgun/api/handlers.py +++ b/nailgun/nailgun/api/handlers.py @@ -193,7 +193,7 @@ class ClusterCollectionHandler(BaseHandler): class ClusterHandler(JSONHandler): - allowed_methods = ('GET', 'PUT') + allowed_methods = ('GET', 'PUT', 'DELETE') model = Cluster fields = ('id', 'name') special_fields = ('nodes', 'release', 'task') @@ -241,6 +241,14 @@ class ClusterHandler(JSONHandler): except ObjectDoesNotExist: return rc.NOT_FOUND + def delete(self, request, cluster_id): + try: + cluster = Cluster.objects.get(id=cluster_id) + cluster.delete() + return rc.DELETED + except ObjectDoesNotExist: + return rc.NOT_FOUND + class NodeCollectionHandler(BaseHandler): @@ -269,7 +277,7 @@ class NodeCollectionHandler(BaseHandler): class NodeHandler(JSONHandler): - allowed_methods = ('GET', 'PUT') + allowed_methods = ('GET', 'PUT', 'DELETE') model = Node fields = ('id', 'name', 'metadata', 'status', 'mac', 'fqdn', 'ip', 'redeployment_needed') @@ -307,6 +315,14 @@ class NodeHandler(JSONHandler): node.save() return NodeHandler.render(node) + def delete(self, request, node_id): + try: + node = Node.objects.get(id=node_id) + node.delete() + return rc.DELETED + except ObjectDoesNotExist: + return rc.NOT_FOUND + class AttributeCollectionHandler(BaseHandler): @@ -531,7 +547,7 @@ class ReleaseCollectionHandler(BaseHandler): class ReleaseHandler(JSONHandler): - allowed_methods = ('GET',) + allowed_methods = ('GET', 'DELETE') model = Release fields = ('id', 'name', 'version', 'description', 'networks_metadata') special_fields = ('roles',) @@ -554,6 +570,14 @@ class ReleaseHandler(JSONHandler): except ObjectDoesNotExist: return rc.NOT_FOUND + def delete(self, request, release_id): + try: + release = Release.objects.get(id=release_id) + release.delete() + return rc.DELETED + except ObjectDoesNotExist: + return rc.NOT_FOUND + class NetworkHandler(JSONHandler): diff --git a/nailgun/nailgun/tasks.py b/nailgun/nailgun/tasks.py index 8d5f8b670..8ee080d52 100644 --- a/nailgun/nailgun/tasks.py +++ b/nailgun/nailgun/tasks.py @@ -137,11 +137,6 @@ def deploy_cluster(cluster_id): add_attrs = {} roles_for_node = node.roles.all() - # TODO(mihgen): It should be possible to have node w/o role assigned - if not roles_for_node: - message = "Task %s failed: Roles list for node %s is empty" \ - % (deploy_cluster.request.id, node.id) - raise EmptyListError(message) node_json['cluster_id'] = cluster_id for f in node._meta.fields: diff --git a/nailgun/nailgun/tests/test_handlers.py b/nailgun/nailgun/tests/test_handlers.py index 05ca86bec..c0bfab744 100644 --- a/nailgun/nailgun/tests/test_handlers.py +++ b/nailgun/nailgun/tests/test_handlers.py @@ -154,6 +154,13 @@ class TestHandlers(TestCase): self.another_node.id) cluster = clusters_from_db[0] self.assertEquals(len(cluster.release.networks.all()), 3) + # test delete + resp = self.client.delete( + reverse('cluster_handler', kwargs={'cluster_id': cluster.id}), + "", + "application/json" + ) + self.assertEquals(resp.status_code, 204) def test_cluster_update(self): updated_name = 'Updated cluster' @@ -207,6 +214,14 @@ class TestHandlers(TestCase): nodes_from_db = Node.objects.filter(id=node_id) self.assertEquals(len(nodes_from_db), 1) + # test delete + resp = self.client.delete( + reverse('node_handler', kwargs={'node_id': node_id}), + "", + "application/json" + ) + self.assertEquals(resp.status_code, 204) + def test_node_creation_using_put(self): node_id = '080000000002' diff --git a/test/integration/helpers.py b/test/integration/helpers.py index 03d1ed18b..b9bd1f32d 100644 --- a/test/integration/helpers.py +++ b/test/integration/helpers.py @@ -67,7 +67,7 @@ class SSHClient(object): def exec_cmd(self, command): - logging.info("Executing command: '%s'" % command) + logging.info("Executing command: '%s'" % command.rstrip()) chan = self.ssh_client.get_transport().open_session() stdin = chan.makefile('wb') stdout = chan.makefile('rb') diff --git a/test/integration/test_node.py b/test/integration/test_node.py index 623dfc375..96d2c2d85 100644 --- a/test/integration/test_node.py +++ b/test/integration/test_node.py @@ -21,7 +21,8 @@ SOLO_PATH = os.path.join(os.path.dirname(__file__), "..", "..", "scripts", "agen DEPLOY_PATH = os.path.join(os.path.dirname(__file__), "..", "..", "bin", "deploy") COOKBOOKS_PATH = os.path.join(os.path.dirname(__file__), "..", "..", "cookbooks") SAMPLE_PATH = os.path.join(os.path.dirname(__file__), "..", "..", "scripts", "ci") -SAMPLE_REMOTE_PATH = "/root" +SAMPLE_REMOTE_PATH = "/home/ubuntu" + class StillPendingException(Exception): pass @@ -34,16 +35,18 @@ class TestNode(TestCase): self.remote = SSHClient() def test_node(self): - cookbook_remote_path = os.path.join(SAMPLE_REMOTE_PATH, "sample-cook") - release_remote_path = os.path.join(SAMPLE_REMOTE_PATH, "sample-release.json") - - logging.info("Starting slave node") admin_node = ci.environment.node['admin'] - admin_ip = admin_node.ip_address - node = ci.environment.node['slave'] - node.start() - - slave_id = node.interfaces[0].mac_address.replace(":", "").upper() + admin_ip = str(admin_node.ip_address) + slave = ci.environment.node['slave'] + slave_id = slave.interfaces[0].mac_address.replace(":", "").upper() + logging.info("Starting slave node") + slave.start() + + self._load_sample_admin( + host=admin_ip, + user="ubuntu", + passwd="r00tme" + ) while True: logging.info("Waiting for slave agent to run...") @@ -56,11 +59,18 @@ class TestNode(TestCase): else: time.sleep(15) - cluster = json.loads(self.client.post( - "http://%s:8000/api/clusters" % admin_ip, - data='{ "name": "MyOwnPrivateCluster", "release": 1 }', - log=True - )) + try: + cluster = json.loads(self.client.get( + "http://%s:8000/api/clusters/1" % admin_ip, + log=True + )) + except ValueError: + logging.info("No clusters found - creating test cluster...") + cluster = json.loads(self.client.post( + "http://%s:8000/api/clusters" % admin_ip, + data='{ "name": "MyOwnPrivateCluster", "release": 2 }', + log=True + )) resp = json.loads(self.client.put( "http://%s:8000/api/clusters/1" % admin_ip, @@ -75,7 +85,7 @@ class TestNode(TestCase): resp = json.loads(self.client.put( "http://%s:8000/api/nodes/%s" % (admin_ip, slave_id), - data='{ "new_roles": [1, 2], "redeployment_needed": true }' + data='{ "new_roles": [2, 3], "redeployment_needed": true }' )) if len(resp["new_roles"]) == 0: raise ValueError("Failed to assign roles to node") @@ -87,13 +97,13 @@ class TestNode(TestCase): )) task_id = task['task_id'] logging.info("Task created: %s" % task_id) - logging.info("Waiting for completion of admin node software installation") + logging.info("Waiting for completion of slave node software installation") while True: try: task = json.loads(self.client.get( "http://%s:8000/api/tasks/%s/" % (admin_ip, task_id) )) - self.check_tasks(task) + self._check_tasks(task) break except StillPendingException: time.sleep(30) @@ -106,58 +116,15 @@ class TestNode(TestCase): wait(lambda: tcp_ping(node["ip"], 22), timeout=1800) self.remote.connect_ssh(node["ip"], "root", "r00tme") - self.remote.rmdir(cookbook_remote_path) - self.remote.rmdir(os.path.join(SAMPLE_REMOTE_PATH, "cookbooks")) - self.remote.rmdir(os.path.join(SAMPLE_REMOTE_PATH, "solo")) - - self.remote.scp( - os.path.join(SAMPLE_PATH, "sample-release.json"), - release_remote_path - ) - - self.remote.mkdir(os.path.join(SAMPLE_REMOTE_PATH, "solo")) - self.remote.mkdir(os.path.join(SAMPLE_REMOTE_PATH, "solo/config")) - - self.remote.scp( - DEPLOY_PATH, - os.path.join(SAMPLE_REMOTE_PATH, "deploy") - ) - self.remote.scp( - os.path.join(SOLO_PATH, "solo.json"), - os.path.join(SAMPLE_REMOTE_PATH, "solo", "config", "solo.json") - ) - self.remote.scp( - os.path.join(SOLO_PATH, "solo.rb"), - os.path.join(SAMPLE_REMOTE_PATH, "solo", "config", "solo.rb") - ) - - self.remote.scp_d( - os.path.join(SAMPLE_PATH, "sample-cook"), - SAMPLE_REMOTE_PATH - ) - self.remote.scp_d( - COOKBOOKS_PATH, - SAMPLE_REMOTE_PATH - ) - - self.remote.exec_cmd("rm /tmp/chef_success") - result = self.remote.exec_cmd("chef-solo -l debug -c %s -j %s" % ( - os.path.join(SAMPLE_REMOTE_PATH, "solo", "config", "solo.rb"), - os.path.join(SAMPLE_REMOTE_PATH, "solo", "config", "solo.json") - )) - if result['exit_status'] != 0: - self.remote.disconnect() - raise Exception("Error while executing chef-solo: %s" % str(result)) - # check if recipes executed - ret = self.remote.exec_cmd("test -f /tmp/chef_success && echo 'SUCCESS'") - if not "SUCCESS" in ret.split("\r\n")[1:]: - raise Exception("Recipe failed to execute!") + ret = self.remote.exec_cmd("test -f /tmp/chef_success") + if ret['exit_status'] != 0: + raise Exception("Recipes failed to execute!") # check recipes execution order ret = self.remote.exec_cmd("cat /tmp/chef_success") - if not ret.split("\r\n")[1:-1] == ['monitor', 'default', 'compute']: + if not ret['stdout'].split("\r\n") == ['monitor', 'default', 'compute']: raise Exception("Recipes executed in a wrong order: %s!" \ - % str(ret.split("\r\n")[1:-1])) + % str(ret['stdout'].split("\r\n"))) """ # check passwords self.remote.exec_cmd("tar -C %s -xvf /root/nodes.tar.gz" % SAMPLE_REMOTE_PATH) @@ -170,7 +137,7 @@ class TestNode(TestCase): self.remote.disconnect() - def check_tasks(self, task): + def _check_tasks(self, task): if task['status'] != 'SUCCESS': if task['status'] == 'PENDING': raise StillPendingException("Task %s is still pending") @@ -180,4 +147,50 @@ class TestNode(TestCase): ) if 'subtasks' in task and task['subtasks']: for subtask in task['subtasks']: - self.check_tasks(subtask) + self._check_tasks(subtask) + + def _load_sample_admin(self, host, user, passwd): + cookbook_remote_path = os.path.join(SAMPLE_REMOTE_PATH, "sample-cook") + release_remote_path = os.path.join(SAMPLE_REMOTE_PATH, "sample-release.json") + self.remote.connect_ssh(host, user, passwd) + self.remote.rmdir(cookbook_remote_path) + self.remote.rmdir(os.path.join(SAMPLE_REMOTE_PATH, "cookbooks")) + self.remote.rmdir(os.path.join(SAMPLE_REMOTE_PATH, "solo")) + self.remote.scp( + os.path.join(SAMPLE_PATH, "sample-release.json"), + release_remote_path + ) + self.remote.mkdir(os.path.join(SAMPLE_REMOTE_PATH, "solo")) + self.remote.mkdir(os.path.join(SAMPLE_REMOTE_PATH, "solo/config")) + self.remote.scp( + os.path.join(SOLO_PATH, "solo.json"), + os.path.join(SAMPLE_REMOTE_PATH, "solo", "config", "solo.json") + ) + self.remote.scp( + os.path.join(SOLO_PATH, "solo.rb"), + os.path.join(SAMPLE_REMOTE_PATH, "solo", "config", "solo.rb") + ) + self.remote.scp_d( + os.path.join(SAMPLE_PATH, "sample-cook"), + SAMPLE_REMOTE_PATH + ) + self.remote.scp_d( + COOKBOOKS_PATH, + SAMPLE_REMOTE_PATH + ) + commands = [ + #"rm -rf /opt/nailgun/nailgun.sqlite", + #"/opt/nailgun-venv/bin/python /opt/nailgun/manage.py syncdb --noinput", + #"chown nailgun:nailgun /opt/nailgun/nailgun.sqlite", + "/opt/nailgun/bin/install_cookbook %s" % cookbook_remote_path, + "/opt/nailgun/bin/create_release %s" % release_remote_path + ] + + with self.remote.sudo: + for cmd in commands: + res = self.remote.exec_cmd(cmd) + if res['exit_status'] != 0: + self.remote.disconnect() + raise Exception("Command failed: %s" % str(res)) + + self.remote.disconnect() \ No newline at end of file