From e75786ca93ea541d6c220e8043ac7e9fc9ac2806 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 6 Jun 2016 08:22:11 -0400 Subject: [PATCH 01/71] client library support for HIL query calls. includes a unit test suite --- haas/client_lib/__init__.py | 0 haas/client_lib/client_lib.py | 91 +++++++ tests/unit/client_lib.py | 498 ++++++++++++++++++++++++++++++++++ 3 files changed, 589 insertions(+) create mode 100644 haas/client_lib/__init__.py create mode 100644 haas/client_lib/client_lib.py create mode 100644 tests/unit/client_lib.py diff --git a/haas/client_lib/__init__.py b/haas/client_lib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/haas/client_lib/client_lib.py b/haas/client_lib/client_lib.py new file mode 100644 index 00000000..a39ac1e3 --- /dev/null +++ b/haas/client_lib/client_lib.py @@ -0,0 +1,91 @@ +""" This module implements the HaaS client library. """ + +import requests +import os +import json +from urlparse import urljoin +from requests.exceptions import ConnectionError + +from client_errors import * + + +class hilClientLib: + """ Main class which contains all the methods to + -- ensure input complies to API requisites + -- generates correct format for server API on behalf of the client + -- parses output from received from the server. + In case of errors recieved from server, it will generate appropriate + appropriate message. + """ + + + def __init__(self, endpoint=None, user=None, password=None): + """ Initialize an instance of the library with following parameters. + endpoint: stands for the http endpoint eg. endpoint=http://127.0.0.1 + user: username as which you wish to connect to HaaS + password: password for the 'user' as decribed above. + Currently all this information is fetched from the user's environment. + """ + self.endpoint = endpoint or os.environ.get('HAAS_ENDPOINT') + self.user = user or os.environ.get('HAAS_USERNAME') + self.password = password or os.environ.get('HAAS_PASSWORD') + + if None in [self.endpoint, self.user, self.password]: + raise LookupError("Insufficient attributes to establish connection with HaaS server") + + def object_url(self, *args): + """Generate URL from combining endpoint and args as relative URL""" + rel = "/".join(args) + url = urljoin(self.endpoint, rel) + return url + + +## ** QUERY COMMANDS ** + def list_free_nodes(self): + """ Reports available nodes in the free_pool.""" + + url = self.object_url('/free_nodes') + try: + query = requests.get(url) + except ConnectionError as err: + print ("ERROR: Either HIL server is not running or some network issue is stopping me from reaching the server ", err) + else: + return query.json + + + + def user_create(self, username, password, role): + """Create a user with password . + It must be assigned a role either 'admin' or 'regular' + """ + #POINT TO DISCUSS: + #Should role default to 'regular' so that users will + #use the extra flag only in case of making other 'admin' users. + + url = object_url('/auth/basic/user', username) + if role not in ('admin', 'regular'): + raise TypeError("role must be either 'admin' or 'regular'") + + ## This is incomplete. Interfacing with corresponding API call needs some thinking. + + def node_register(node, subtype, *args): + """Register a node named , with the given type + eg. If obm if of type: ipmi then provide arguments relevant to the driver + "ipmi", , , + """ + #FIXME: This needs to have a corresponding API provided by the drivers themselves + #Also, there has to be a way to expose activated drivers to Users. + #Library should be able to provide correct handles generic hooks for all of the drivers. + #My General feeling is that api library should know minimal about the server. + #Currently it violates the Model-View-Controller paradigm, since it has to know + #Which drivers are available and active on the server and what does the third party driver + #api name looks like. Library should not know all this. + + obm_api = "http://schema.massopencloud.org/haas/v0/obm/" + + + + + + + diff --git a/tests/unit/client_lib.py b/tests/unit/client_lib.py new file mode 100644 index 00000000..22c593e3 --- /dev/null +++ b/tests/unit/client_lib.py @@ -0,0 +1,498 @@ +# Copyright 2013-2014 Massachusetts Open Cloud Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS +# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language +# governing permissions and limitations under the License. + +"""Unit tests for api.py""" +import haas +from haas import model, api, deferred, server, config +from haas.model import db +from haas.test_common import * +from haas.network_allocator import get_network_allocator +import pytest +import json + +import requests +import os +import subprocess +from urlparse import urljoin +from requests.exceptions import ConnectionError +from haas.client_lib.client_lib import hilClientLib + +MOCK_SWITCH_TYPE = 'http://schema.massopencloud.org/haas/v0/switches/mock' +OBM_TYPE_MOCK = 'http://schema.massopencloud.org/haas/v0/obm/mock' +OBM_TYPE_IPMI = 'http://schema.massopencloud.org/haas/v0/obm/ipmi' + +HAAS_ENDPOINT="http://127.0.0.1" +HAAS_USERNAME="hil_user" +HAAS_PASSWORD="hil_pass1234" +h = hilClientLib(HAAS_ENDPOINT, HAAS_USERNAME, HAAS_PASSWORD) #Instantiating the client library. + + +@pytest.fixture +def configure(): + config_testsuite() + config_merge({ + 'extensions': { + 'haas.ext.switches.mock': '', + 'haas.ext.obm.ipmi': '', + 'haas.ext.obm.mock': '', + }, + }) + config.load_extensions() + + +fresh_database = pytest.fixture(fresh_database) + + +@pytest.fixture +def server_init(): + server.register_drivers() + server.validate_state() + + +with_request_context = pytest.yield_fixture(with_request_context) + + +pytestmark = pytest.mark.usefixtures('configure', + 'fresh_database', + 'server_init', + 'with_request_context') + + + + +class Test_hilClientlib: + """Tests if the hil client library object instantiates correctly""" + + HAAS_ENDPOINT="http://127.0.0.1" + HAAS_USERNAME="hil_user" + HAAS_PASSWORD="hil_pass1234" + + def create_env(self): + """ source required parameters in the environment """ + with open("/tmp/hil_env", 'w') as f: + f.write("export HAAS_ENDPOINT=up_the_HIL; export HAAS_USERNAME=jack; export HAAS_PASSWORD=broke_his_crown\n") + command = ['bash', '-c', 'source /tmp/hil_env && env'] + proc = subprocess.Popen(command, stdout = subprocess.PIPE) + + for line in proc.stdout: + (key, _, value) = line.partition("=") + if key in ['HAAS_ENDPOINT', 'HAAS_USERNAME', 'HAAS_PASSWORD']: + value = value.strip('\n') + os.environ[key] = value + + + def test_parameter_passing(self): + h = hilClientLib(HAAS_ENDPOINT, HAAS_USERNAME, HAAS_PASSWORD) #Instantiating the client library. + assert h.endpoint == self.HAAS_ENDPOINT + assert h.user == self.HAAS_USERNAME + assert h.password == self.HAAS_PASSWORD + + def test_env_parameter_passing(self): + self.create_env() + h = hilClientLib() #Instantiating client library with values from user env. + assert h.endpoint == "up_the_HIL" + assert h.user == "jack" + assert h.password == "broke_his_crown" + + + + + +class TestQuery: + """test the query api""" + + def _compare_node_dumps(self, actual, expected): + """This is a helper method which compares the parsed json output of + two show_headnode calls for equality. There are a couple issue to work + around to get an accurate result - in particular, we often don't care + about the order of lists, which needs special handling (especially when + the arguments aren't orderable). + """ + # For two lists to be equal, their elements have to be in the same + # order. However, there is no ordering defined on dictionaries, so we + # can't just sort the lists. instead we check our desired notion of + # equality manually, and then clear both hnic lists before comparing + # the rest of the data: + for nic in actual['nics']: + assert nic in expected['nics'] + expected['nics'].remove(nic) + assert len(expected['nics']) == 0 + actual['nics'] = [] + assert expected == actual + + def test_free_nodes(self): + api.node_register('master-control-program', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + api.node_register('robocop', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + api.node_register('data', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + result = json.loads(api.list_free_nodes()) + # For the lists to be equal, the ordering must be the same: + result.sort() + assert result == [ + 'data', + 'master-control-program', + 'robocop', + ] + + def test_list_projects(self): + assert json.loads(api.list_projects()) == [] + api.project_create('anvil-nextgen') + assert json.loads(api.list_projects()) == ['anvil-nextgen'] + api.project_create('runway') + api.project_create('manhattan') + assert sorted(json.loads(api.list_projects())) == [ + 'anvil-nextgen', + 'manhattan', + 'runway', + ] + + def test_no_free_nodes(self): + assert json.loads(api.list_free_nodes()) == [] + + def test_some_non_free_nodes(self): + """Make sure that allocated nodes don't show up in the free list.""" + api.node_register('master-control-program', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + api.node_register('robocop', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + api.node_register('data', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + + api.project_create('anvil-nextgen') + api.project_connect_node('anvil-nextgen', 'robocop') + api.project_connect_node('anvil-nextgen', 'data') + + assert json.loads(api.list_free_nodes()) == ['master-control-program'] + + def test_show_node(self): + """Test the show_node api call. + + We create a node, and query it twice: once before it is reserved, + and once after it has been reserved by a project and attached to + a network. Two things should change: (1) "project" should show registered project, + and (2) the newly attached network should be listed. + """ + api.node_register('robocop', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + api.node_register_nic('robocop', 'eth0', 'DE:AD:BE:EF:20:14') + api.node_register_nic('robocop', 'wlan0', 'DE:AD:BE:EF:20:15') + + actual = json.loads(api.show_node('robocop')) + expected = { + 'name': 'robocop', + 'project': None, + 'nics': [ + { + 'label':'eth0', + 'macaddr': 'DE:AD:BE:EF:20:14', + "networks": {} + }, + { + 'label':'wlan0', + 'macaddr': 'DE:AD:BE:EF:20:15', + "networks": {} + } + ], + } + self._compare_node_dumps(actual, expected) + + def test_show_node(self): + """Test the show_node api call. + We create a node, and query it twice: once before it is reserved, + and once after it has been reserved by a project and attached to + a network. Two things should change: (1) "project" should show registered project, + and (2) the newly attached network should be listed. + """ + + def test_show_node_unavailable(self): + api.node_register('robocop', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + api.node_register_nic('robocop', 'eth0', 'DE:AD:BE:EF:20:14') + api.node_register_nic('robocop', 'wlan0', 'DE:AD:BE:EF:20:15') + + actual = json.loads(api.show_node('robocop')) + expected = { + 'name': 'robocop', + 'project': None, + 'nics': [ + { + 'label':'eth0', + 'macaddr': 'DE:AD:BE:EF:20:14', + "networks": {} + }, + { + 'label':'wlan0', + 'macaddr': 'DE:AD:BE:EF:20:15', + "networks": {} + } + ], + } + self._compare_node_dumps(actual, expected) + + def test_show_node_multiple_network(self): + api.node_register('robocop', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + api.node_register_nic('robocop', 'eth0', 'DE:AD:BE:EF:20:14') + api.node_register_nic('robocop', 'wlan0', 'DE:AD:BE:EF:20:15') + + api.project_create('anvil-nextgen') + api.project_connect_node('anvil-nextgen', 'robocop') + network_create_simple('pxe', 'anvil-nextgen') + api.node_connect_network('robocop', 'eth0', 'pxe') + network_create_simple('storage', 'anvil-nextgen') + api.node_connect_network('robocop', 'wlan0', 'storage') + deferred.apply_networking() + + actual = json.loads(api.show_node('robocop')) + expected = { + 'name': 'robocop', + 'project':'anvil-nextgen', + 'nics': [ + { + 'label': 'eth0', + 'macaddr': 'DE:AD:BE:EF:20:14', + "networks": { + get_network_allocator().get_default_channel(): 'pxe' + } + }, + { + 'label': 'wlan0', + 'macaddr': 'DE:AD:BE:EF:20:15', + "networks": { + get_network_allocator().get_default_channel(): 'storage' + } + } + ], + } + self._compare_node_dumps(actual, expected) + + def test_show_nonexistant_node(self): + with pytest.raises(api.NotFoundError): + api.show_node('master-control-program') + + def test_project_nodes_exist(self): + api.node_register('master-control-program', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + api.node_register('robocop', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + api.node_register('data', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + + api.project_create('anvil-nextgen') + api.project_connect_node('anvil-nextgen', 'master-control-program') + api.project_connect_node('anvil-nextgen', 'robocop') + api.project_connect_node('anvil-nextgen', 'data') + result = json.loads(api.list_project_nodes('anvil-nextgen')) + # For the lists to be equal, the ordering must be the same: + result.sort() + assert result == [ + 'data', + 'master-control-program', + 'robocop', + ] + + def test_project_headnodes_exist(self): + api.project_create('anvil-nextgen') + api.headnode_create('hn0', 'anvil-nextgen', 'base-headnode') + api.headnode_create('hn1', 'anvil-nextgen', 'base-headnode') + api.headnode_create('hn2', 'anvil-nextgen', 'base-headnode') + + result = json.loads(api.list_project_headnodes('anvil-nextgen')) + # For the lists to be equal, the ordering must be the same: + result.sort() + assert result == [ + 'hn0', + 'hn1', + 'hn2', + ] + + def test_no_project_nodes(self): + api.project_create('anvil-nextgen') + assert json.loads(api.list_project_nodes('anvil-nextgen')) == [] + + def test_no_project_headnodes(self): + api.project_create('anvil-nextgen') + assert json.loads(api.list_project_headnodes('anvil-nextgen')) == [] + + def test_some_nodes_in_project(self): + """Test that only assigned nodes are in the project.""" + api.node_register('master-control-program', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + api.node_register('robocop', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + api.node_register('data', obm={ + "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", + "host": "ipmihost", + "user": "root", + "password": "tapeworm"}) + + api.project_create('anvil-nextgen') + api.project_connect_node('anvil-nextgen', 'robocop') + api.project_connect_node('anvil-nextgen', 'data') + + result = json.loads(api.list_project_nodes('anvil-nextgen')) + result.sort() + assert result == ['data', 'robocop'] + + def test_project_list_networks(self): + api.project_create('anvil-nextgen') + + network_create_simple('pxe', 'anvil-nextgen') + network_create_simple('public', 'anvil-nextgen') + network_create_simple('private', 'anvil-nextgen') + + result = json.loads(api.list_project_networks('anvil-nextgen')) + # For the lists to be equal, the ordering must be the same: + result.sort() + assert result == [ + 'private', + 'public', + 'pxe' + ] + + def test_no_project_networks(self): + api.project_create('anvil-nextgen') + assert json.loads(api.list_project_nodes('anvil-nextgen')) == [] + + import uuid + + def test_show_headnode(self): + api.project_create('anvil-nextgen') + network_create_simple('spiderwebs', 'anvil-nextgen') + api.headnode_create('BGH', 'anvil-nextgen', 'base-headnode') + api.headnode_create_hnic('BGH', 'eth0') + api.headnode_create_hnic('BGH', 'wlan0') + api.headnode_connect_network('BGH', 'eth0', 'spiderwebs') + + + result = json.loads(api.show_headnode('BGH')) + + # Verify UUID is well formed, then delete it, since we can't match it + # exactly in the check below + temp = uuid.UUID(result['uuid']) + del result['uuid'] + + # For the lists to be equal, the ordering must be the same: + result['hnics'].sort() + assert result == { + 'name': 'BGH', + 'project': 'anvil-nextgen', + 'base_img': 'base-headnode', + 'hnics': [ + 'eth0', + 'wlan0', + ], + 'vncport': None + } + + def test_show_nonexistant_headnode(self): + with pytest.raises(api.NotFoundError): + api.show_headnode('BGH') + + + def test_list_headnode_images(self): + result = json.loads(api.list_headnode_images()) + assert result == [ 'base-headnode', 'img1', 'img2', 'img3', 'img4' ] + + +class Test_show_network: + """Test the show_network api cal.""" + + def test_show_network_simple(self): + api.project_create('anvil-nextgen') + network_create_simple('spiderwebs', 'anvil-nextgen') + + result = json.loads(api.show_network('spiderwebs')) + assert result == { + 'name': 'spiderwebs', + 'creator': 'anvil-nextgen', + 'access': 'anvil-nextgen', + "channels": ["null"] + } + + def test_show_network_public(self): + api.network_create('public-network', + creator='admin', + access='', + net_id='432') + + result = json.loads(api.show_network('public-network')) + assert result == { + 'name': 'public-network', + 'creator': 'admin', + 'channels': ['null'], + } + + def test_show_network_provider(self): + api.project_create('anvil-nextgen') + api.network_create('spiderwebs', + creator='admin', + access='anvil-nextgen', + net_id='451') + + result = json.loads(api.show_network('spiderwebs')) + assert result == { + 'name': 'spiderwebs', + 'creator': 'admin', + 'access': 'anvil-nextgen', + 'channels': ['null'], + } + + + + From 61bb87d5971d5094b23a78b593dcd5f21f184c32 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 20 Jun 2016 02:44:47 -0400 Subject: [PATCH 02/71] Adds files containing common code to build client library. --- haas/client/auth.py | 13 ++++++++++++ haas/client/base.py | 41 ++++++++++++++++++++++++++++++++++++ haas/client/client.py | 13 ++++++++++++ haas/client/client_errors.py | 4 ++++ haas/client/node.py | 22 +++++++++++++++++++ 5 files changed, 93 insertions(+) create mode 100644 haas/client/auth.py create mode 100644 haas/client/base.py create mode 100644 haas/client/client.py create mode 100644 haas/client/client_errors.py create mode 100644 haas/client/node.py diff --git a/haas/client/auth.py b/haas/client/auth.py new file mode 100644 index 00000000..98a64242 --- /dev/null +++ b/haas/client/auth.py @@ -0,0 +1,13 @@ +import base64 + + + +def auth_db(username, password): + concatit = username+":"+password + concatit64 = base64.b64encode(concatit) + + return concatit64 + + + + diff --git a/haas/client/base.py b/haas/client/base.py new file mode 100644 index 00000000..6860dba8 --- /dev/null +++ b/haas/client/base.py @@ -0,0 +1,41 @@ +""" This module implements the HaaS client library. """ + +import requests +import os +import json +from urlparse import urljoin +#from requests.exceptions import ConnectionError, MissingSchema + +from client_errors import * + + +class ClientBase(object): + """ Main class which contains all the methods to + -- ensure input complies to API requisites + -- generates correct format for server API on behalf of the client + -- parses output from received from the server. + In case of errors recieved from server, it will generate appropriate + appropriate message. + """ + + + def __init__(self, endpoint=None, auth=None): + """ Initialize an instance of the library with following parameters. + endpoint: stands for the http endpoint eg. endpoint=http://127.0.0.1 + user: username as which you wish to connect to HaaS + password: password for the 'user' as decribed above. + Currently all this information is fetched from the user's environment. + """ + self.endpoint = endpoint + self.auth = auth + + if None in [self.endpoint, self.auth]: + raise LookupError("Insufficient attributes to establish connection with HaaS server") + + def object_url(self, *args): + """Generate URL from combining endpoint and args as relative URL""" + rel = "/".join(args) + url = urljoin(self.endpoint, rel) + return url + + diff --git a/haas/client/client.py b/haas/client/client.py new file mode 100644 index 00000000..fc8d3a31 --- /dev/null +++ b/haas/client/client.py @@ -0,0 +1,13 @@ +from haas.client.base import ClientBase +from haas.client.node import Node +from haas.client.auth import auth_db + + + +class Client(object): + + def __init__(self, endpoint, auth): + self.endpoint = endpoint + self.auth = auth + self.node = Node(self.endpoint, self.auth) + diff --git a/haas/client/client_errors.py b/haas/client/client_errors.py new file mode 100644 index 00000000..a182fc42 --- /dev/null +++ b/haas/client/client_errors.py @@ -0,0 +1,4 @@ +class AuthenticationError(Exception): + pass + + diff --git a/haas/client/node.py b/haas/client/node.py new file mode 100644 index 00000000..9c9e4645 --- /dev/null +++ b/haas/client/node.py @@ -0,0 +1,22 @@ +from haas.client.base import * +from haas.client.client_errors import * + + +class Node(ClientBase): + """ Consists of calls to query and manipulate node related + objects and relations """ + + def free_list(self): + """ List all nodes that HIL manages """ + url = self.object_url('/free_nodes') + q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + if q.ok: + return q.json() + elif q.status_code == 401: + raise AuthenticationError("Make sure credentials match chosen authentication backend.") + + + + + + From ee5aa40af142819d53407146e4ef7d5d46e0fcec Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 20 Jun 2016 04:19:26 -0400 Subject: [PATCH 03/71] Adds client library support for query calls --- haas/cli.py | 23 +++++++++++++-- haas/client/client.py | 4 +++ haas/client/client_errors.py | 2 ++ haas/client/project.py | 56 ++++++++++++++++++++++++++++++++++++ haas/client/switch.py | 22 ++++++++++++++ 5 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 haas/client/project.py create mode 100644 haas/client/switch.py diff --git a/haas/cli.py b/haas/cli.py index 6535fbc5..3250b2a1 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -26,6 +26,21 @@ from functools import wraps +## Hook to the client library +from haas.client.auth import * +from haas.client.client import Client + + +ep = "http://127.0.0.1:5000" or os.environ.get('HAAS_ENDPOINT') +username = "jil" or os.environ.get('HAAS_USERNAME') +password = "tumbling" or os.environ.get('HAAS_PASSWORD') + + +auth = auth_db(username, password) + +C = Client(ep, auth) #Initializing client library + + command_dict = {} usage_dict = {} MIN_PORT_NUMBER = 1 @@ -454,11 +469,13 @@ def port_detach_nic(switch, port): url = object_url('switch', switch, 'port', port, 'detach_nic') do_post(url) -@cmd +#@cmd def list_free_nodes(): """List all free nodes""" - url = object_url('free_nodes') - do_get(url) + q = C.node.free_list + sys.stdout.write(q) +# url = object_url('free_nodes') +# do_get(url) @cmd def list_project_nodes(project): diff --git a/haas/client/client.py b/haas/client/client.py index fc8d3a31..72d756c3 100644 --- a/haas/client/client.py +++ b/haas/client/client.py @@ -1,5 +1,7 @@ from haas.client.base import ClientBase from haas.client.node import Node +from haas.client.project import Project +from haas.client.switch import Switch from haas.client.auth import auth_db @@ -10,4 +12,6 @@ def __init__(self, endpoint, auth): self.endpoint = endpoint self.auth = auth self.node = Node(self.endpoint, self.auth) + self.project = Project(self.endpoint, self.auth) + self.switch = Switch(self.endpoint, self.auth) diff --git a/haas/client/client_errors.py b/haas/client/client_errors.py index a182fc42..2fc63a87 100644 --- a/haas/client/client_errors.py +++ b/haas/client/client_errors.py @@ -1,4 +1,6 @@ class AuthenticationError(Exception): pass +class NotFoundError(Exception): + pass diff --git a/haas/client/project.py b/haas/client/project.py new file mode 100644 index 00000000..a47530c2 --- /dev/null +++ b/haas/client/project.py @@ -0,0 +1,56 @@ +from haas.client.base import * +from haas.client.client_errors import * + + +class Project(ClientBase): + """ Consists of calls to query and manipulate project related + objects and relations """ + + def list(self): + """ Lists all projects under HIL """ + url = self.object_url('/projects') + q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + if q.ok: + return q.json() + elif q.status_code == 401: + raise AuthenticationError("Make sure credentials match chosen authentication backend.") + + def _for_project(self, project_name): + """ Lists all nodes allocated to a given """ + + self.project_name = project_name + project_root = "/project/"+self.project_name + return project_root + + def nodes_in(self, project_name): + """ Lists nodes allocated to project """ + self.project_name = project_name + base_url = self. _for_project(self.project_name) + node_list = base_url+"/nodes" + url = self.object_url(node_list) + q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + if q.ok: + return q.json() + elif q.status_code == 401: + raise AuthenticationError("Invalid credentials.") + elif q.status_code == 404: + raise NotFoundError("Project %s does not exist." % self.project_name) + + + def networks_in(self, project_name): + """ Lists nodes allocated to project """ + self.project_name = project_name + base_url = self. _for_project(self.project_name) + node_list = base_url+"/networks" + url = self.object_url(node_list) + q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + if q.ok: + return q.json() + elif q.status_code == 401: + raise AuthenticationError("Invalid credentials.") + elif q.status_code == 404: + raise NotFoundError("Project %s does not exist." % self.project_name) + + + + diff --git a/haas/client/switch.py b/haas/client/switch.py new file mode 100644 index 00000000..ab981fb1 --- /dev/null +++ b/haas/client/switch.py @@ -0,0 +1,22 @@ +from haas.client.base import * +from haas.client.client_errors import * + + +class Switch(ClientBase): + """ Consists of calls to query and manipulate node related + objects and relations """ + + def list(self): + """ List all nodes that HIL manages """ + url = self.object_url('/switches') + q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + if q.ok: + return q.json() + elif q.status_code == 401: + raise AuthenticationError("Make sure credentials match chosen authentication backend.") + + + + + + From 529b37cd30f7028cddc74e28ef38efd096f5a7dc Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Sun, 3 Jul 2016 00:21:44 -0400 Subject: [PATCH 04/71] Adds client support to CLI. --- haas/cli.py | 26 +++++++++++++++----------- haas/client/node.py | 13 +++++++++++++ haas/client/project.py | 19 ++++++------------- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 3250b2a1..5091544a 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -210,8 +210,8 @@ def user_delete(username): @cmd def list_projects(): """List all projects""" - url = object_url('projects') - do_get(url) + q = C.project.list() + sys.stdout.write('%s Projects : ' %len(q) + " ".join(q) + '\n') @cmd def user_add_project(user, project): @@ -442,8 +442,10 @@ def switch_delete(switch): @cmd def list_switches(): """List all switches""" - url = object_url('switches') - do_get(url) + q = C.switch.list() + sys.stdout.write('%s switches : ' %len(q) + " ".join(q) + '\n') +# url = object_url('switches') +# do_get(url) @cmd def port_register(switch, port): @@ -469,19 +471,21 @@ def port_detach_nic(switch, port): url = object_url('switch', switch, 'port', port, 'detach_nic') do_post(url) -#@cmd +@cmd def list_free_nodes(): """List all free nodes""" - q = C.node.free_list - sys.stdout.write(q) -# url = object_url('free_nodes') -# do_get(url) + q = C.node.free_list() + sys.stdout.write('%s Nodes available in free pool: ' %len(q) + " ".join(q) + '\n') @cmd def list_project_nodes(project): """List all nodes attached to a """ - url = object_url('project', project, 'nodes') - do_get(url) + q = C.project.nodes_in(project) + sys.stdout.write('Nodes allocated to %s: ' %project + " ".join(q) + '\n') +# print q + +# url = object_url('project', project, 'nodes') +# do_get(url) @cmd def list_project_networks(project): diff --git a/haas/client/node.py b/haas/client/node.py index 9c9e4645..e99c95ab 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -15,6 +15,19 @@ def free_list(self): elif q.status_code == 401: raise AuthenticationError("Make sure credentials match chosen authentication backend.") + def show_node(self, node_name): + """ Shows attributes of a given node """ + + self.node_name = node_name + l = ['node', node_name ] + custom_url = "/"+"/".join(l) + url = self.object_url(custom_url) + q = requests.get(url, headers={"Authorization": "Basic %s" %self.auth}) + return q.json() + + + + diff --git a/haas/client/project.py b/haas/client/project.py index a47530c2..bc14c473 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -15,19 +15,12 @@ def list(self): elif q.status_code == 401: raise AuthenticationError("Make sure credentials match chosen authentication backend.") - def _for_project(self, project_name): - """ Lists all nodes allocated to a given """ - - self.project_name = project_name - project_root = "/project/"+self.project_name - return project_root - def nodes_in(self, project_name): """ Lists nodes allocated to project """ self.project_name = project_name - base_url = self. _for_project(self.project_name) - node_list = base_url+"/nodes" - url = self.object_url(node_list) + l = [ 'project', self.project_name, 'nodes' ] + custom_url = "/"+"/".join(l) + url = self.object_url(custom_url) q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) if q.ok: return q.json() @@ -40,9 +33,9 @@ def nodes_in(self, project_name): def networks_in(self, project_name): """ Lists nodes allocated to project """ self.project_name = project_name - base_url = self. _for_project(self.project_name) - node_list = base_url+"/networks" - url = self.object_url(node_list) + l = [ 'project', self.project_name, 'nodes' ] + custom_url = "/"+"/".join(l) + url = self.object_url(custom_url) q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) if q.ok: return q.json() From e5cdad3df07b0b2222eb083558416187c9268921 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 4 Jul 2016 20:06:24 -0400 Subject: [PATCH 05/71] Adds -- code for show_xx calls. -- Some code refinement --- haas/cli.py | 21 ++++++++++++++------- haas/client/node.py | 4 +--- haas/client/project.py | 8 ++------ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 5091544a..db2f1fcc 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -490,20 +490,27 @@ def list_project_nodes(project): @cmd def list_project_networks(project): """List all networks attached to a """ - url = object_url('project', project, 'networks') - do_get(url) + q = C.project.networks_in(project) + sys.stdout.write("Networks allocated to {}\t: {}\n".format(project, " ".join(q))) +# url = object_url('project', project, 'networks') +# do_get(url) @cmd def show_network(network): """Display information about """ - url = object_url('network', network) - do_get(url) + q = C.project.networks_in(project) + sys.stdout.write("Networks allocated to {}\t: {}\n".format(project, " ".join(q))) +# url = object_url('network', network) +# do_get(url) @cmd def show_node(node): - """Display information about a """ - url = object_url('node', node) - do_get(url) + """Display information about a + FIXME: Recursion should be implemented to the output. + """ + q = C.node.show_node(node) + for item in q.items(): + sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) @cmd def list_project_headnodes(project): diff --git a/haas/client/node.py b/haas/client/node.py index e99c95ab..3ec3da5b 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -19,9 +19,7 @@ def show_node(self, node_name): """ Shows attributes of a given node """ self.node_name = node_name - l = ['node', node_name ] - custom_url = "/"+"/".join(l) - url = self.object_url(custom_url) + url = self.object_url('node', node_name) q = requests.get(url, headers={"Authorization": "Basic %s" %self.auth}) return q.json() diff --git a/haas/client/project.py b/haas/client/project.py index bc14c473..a157d29e 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -18,9 +18,7 @@ def list(self): def nodes_in(self, project_name): """ Lists nodes allocated to project """ self.project_name = project_name - l = [ 'project', self.project_name, 'nodes' ] - custom_url = "/"+"/".join(l) - url = self.object_url(custom_url) + url = self.object_url('project', self.project_name, 'nodes') q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) if q.ok: return q.json() @@ -33,9 +31,7 @@ def nodes_in(self, project_name): def networks_in(self, project_name): """ Lists nodes allocated to project """ self.project_name = project_name - l = [ 'project', self.project_name, 'nodes' ] - custom_url = "/"+"/".join(l) - url = self.object_url(custom_url) + url = self.object_url('project', self.project_name, 'networks') q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) if q.ok: return q.json() From 060a55e37d16f6cb8776c49ca882e1093703e246 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 5 Jul 2016 01:59:58 -0400 Subject: [PATCH 06/71] Adds unit tests and some code refactor. --- haas/client/client.py | 2 + haas/client/node.py | 2 + haas/client/project.py | 2 - tests/unit/client.py | 129 ++++++++++ tests/unit/client_lib.py | 498 --------------------------------------- 5 files changed, 133 insertions(+), 500 deletions(-) create mode 100644 tests/unit/client.py delete mode 100644 tests/unit/client_lib.py diff --git a/haas/client/client.py b/haas/client/client.py index 72d756c3..0a2ae01a 100644 --- a/haas/client/client.py +++ b/haas/client/client.py @@ -2,6 +2,7 @@ from haas.client.node import Node from haas.client.project import Project from haas.client.switch import Switch +#from haas.client.network import Network from haas.client.auth import auth_db @@ -14,4 +15,5 @@ def __init__(self, endpoint, auth): self.node = Node(self.endpoint, self.auth) self.project = Project(self.endpoint, self.auth) self.switch = Switch(self.endpoint, self.auth) +# self.network = Network(self.endpoint, self.auth) diff --git a/haas/client/node.py b/haas/client/node.py index 3ec3da5b..ec501315 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -9,6 +9,7 @@ class Node(ClientBase): def free_list(self): """ List all nodes that HIL manages """ url = self.object_url('/free_nodes') + print url q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) if q.ok: return q.json() @@ -20,6 +21,7 @@ def show_node(self, node_name): self.node_name = node_name url = self.object_url('node', node_name) + print url q = requests.get(url, headers={"Authorization": "Basic %s" %self.auth}) return q.json() diff --git a/haas/client/project.py b/haas/client/project.py index a157d29e..8e7ef784 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -41,5 +41,3 @@ def networks_in(self, project_name): raise NotFoundError("Project %s does not exist." % self.project_name) - - diff --git a/tests/unit/client.py b/tests/unit/client.py new file mode 100644 index 00000000..d6473b2b --- /dev/null +++ b/tests/unit/client.py @@ -0,0 +1,129 @@ +# Copyright 2013-2014 Massachusetts Open Cloud Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS +# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language +# governing permissions and limitations under the License. + +"""Unit tests for api.py""" +import haas +from haas import model, api, deferred, server, config +from haas.model import db +from haas.test_common import * +from haas.network_allocator import get_network_allocator +import pytest +import json + +import requests +import os +import subprocess +from urlparse import urljoin +from requests.exceptions import ConnectionError +#from haas.client_lib.client_lib import hilClientLib +## Hook to the client library +from haas.client.base import ClientBase +from haas.client.auth import * +from haas.client.client import Client + + + +MOCK_SWITCH_TYPE = 'http://schema.massopencloud.org/haas/v0/switches/mock' +OBM_TYPE_MOCK = 'http://schema.massopencloud.org/haas/v0/obm/mock' +OBM_TYPE_IPMI = 'http://schema.massopencloud.org/haas/v0/obm/ipmi' + +ep = "http://127.0.0.1" or os.environ.get('HAAS_ENDPOINT') +username = "hil_user" or os.environ.get('HAAS_USERNAME') +password = "hil_pass1234" or os.environ.get('HAAS_PASSWORD') + + +auth = auth_db(username, password) + +C = Client(ep, auth) #Initializing client library + + +@pytest.fixture +def configure(): + config_testsuite() + config_merge({ + 'extensions': { + 'haas.ext.auth.mock' : '', +# This extension is enabled by default in the tests, so we need to +# disable it explicitly: + 'haas.ext.auth.null': None, + 'haas.ext.switches.mock': '', + 'haas.ext.obm.ipmi': '', + 'haas.ext.obm.mock': '', + }, + }) + config.load_extensions() + + +fresh_database = pytest.fixture(fresh_database) + + +@pytest.fixture +def server_init(): + server.register_drivers() + server.validate_state() + + +with_request_context = pytest.yield_fixture(with_request_context) + + +pytestmark = pytest.mark.usefixtures('configure', + 'fresh_database', + 'server_init', + 'with_request_context') + + +def test_auth_db(): + auth = auth_db(username, password) + assert auth.decode('base64', 'strict') == (":").join([username, password]) + + +def test_init_error(): + try: + x = ClientBase() + except LookupError: + assert True + + +def test_correct_init(): + x = ClientBase(ep, 'some_base64_string') + assert x.endpoint == "http://127.0.0.1" + assert x.auth == "some_base64_string" + +def test_object_url(): + x = ClientBase(ep, 'some_base64_string') + y = x.object_url('abc', '123', 'xy23z') + assert y == 'http://127.0.0.1/abc/123/xy23z' + + + +#class TestNodeOperations: +# """Tests for the haas.client.node.* operations. """ +# +# def test_list_free_nodes(self): +# api.node_register('master-control-program', obm={ +# "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", +# "host": "ipmihost", +# "user": "root", +# "password": "tapeworm"}) +# api.node_register('robocop', obm={ +# "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", +# "host": "ipmihost", +# "user": "root", +# "password": "tapeworm"}) +# api.node_register('data', obm={ +# "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", +# "host": "ipmihost", +# "user": "root", +# "password": "tapeworm"}) + diff --git a/tests/unit/client_lib.py b/tests/unit/client_lib.py deleted file mode 100644 index 22c593e3..00000000 --- a/tests/unit/client_lib.py +++ /dev/null @@ -1,498 +0,0 @@ -# Copyright 2013-2014 Massachusetts Open Cloud Contributors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the -# License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an "AS -# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -# express or implied. See the License for the specific language -# governing permissions and limitations under the License. - -"""Unit tests for api.py""" -import haas -from haas import model, api, deferred, server, config -from haas.model import db -from haas.test_common import * -from haas.network_allocator import get_network_allocator -import pytest -import json - -import requests -import os -import subprocess -from urlparse import urljoin -from requests.exceptions import ConnectionError -from haas.client_lib.client_lib import hilClientLib - -MOCK_SWITCH_TYPE = 'http://schema.massopencloud.org/haas/v0/switches/mock' -OBM_TYPE_MOCK = 'http://schema.massopencloud.org/haas/v0/obm/mock' -OBM_TYPE_IPMI = 'http://schema.massopencloud.org/haas/v0/obm/ipmi' - -HAAS_ENDPOINT="http://127.0.0.1" -HAAS_USERNAME="hil_user" -HAAS_PASSWORD="hil_pass1234" -h = hilClientLib(HAAS_ENDPOINT, HAAS_USERNAME, HAAS_PASSWORD) #Instantiating the client library. - - -@pytest.fixture -def configure(): - config_testsuite() - config_merge({ - 'extensions': { - 'haas.ext.switches.mock': '', - 'haas.ext.obm.ipmi': '', - 'haas.ext.obm.mock': '', - }, - }) - config.load_extensions() - - -fresh_database = pytest.fixture(fresh_database) - - -@pytest.fixture -def server_init(): - server.register_drivers() - server.validate_state() - - -with_request_context = pytest.yield_fixture(with_request_context) - - -pytestmark = pytest.mark.usefixtures('configure', - 'fresh_database', - 'server_init', - 'with_request_context') - - - - -class Test_hilClientlib: - """Tests if the hil client library object instantiates correctly""" - - HAAS_ENDPOINT="http://127.0.0.1" - HAAS_USERNAME="hil_user" - HAAS_PASSWORD="hil_pass1234" - - def create_env(self): - """ source required parameters in the environment """ - with open("/tmp/hil_env", 'w') as f: - f.write("export HAAS_ENDPOINT=up_the_HIL; export HAAS_USERNAME=jack; export HAAS_PASSWORD=broke_his_crown\n") - command = ['bash', '-c', 'source /tmp/hil_env && env'] - proc = subprocess.Popen(command, stdout = subprocess.PIPE) - - for line in proc.stdout: - (key, _, value) = line.partition("=") - if key in ['HAAS_ENDPOINT', 'HAAS_USERNAME', 'HAAS_PASSWORD']: - value = value.strip('\n') - os.environ[key] = value - - - def test_parameter_passing(self): - h = hilClientLib(HAAS_ENDPOINT, HAAS_USERNAME, HAAS_PASSWORD) #Instantiating the client library. - assert h.endpoint == self.HAAS_ENDPOINT - assert h.user == self.HAAS_USERNAME - assert h.password == self.HAAS_PASSWORD - - def test_env_parameter_passing(self): - self.create_env() - h = hilClientLib() #Instantiating client library with values from user env. - assert h.endpoint == "up_the_HIL" - assert h.user == "jack" - assert h.password == "broke_his_crown" - - - - - -class TestQuery: - """test the query api""" - - def _compare_node_dumps(self, actual, expected): - """This is a helper method which compares the parsed json output of - two show_headnode calls for equality. There are a couple issue to work - around to get an accurate result - in particular, we often don't care - about the order of lists, which needs special handling (especially when - the arguments aren't orderable). - """ - # For two lists to be equal, their elements have to be in the same - # order. However, there is no ordering defined on dictionaries, so we - # can't just sort the lists. instead we check our desired notion of - # equality manually, and then clear both hnic lists before comparing - # the rest of the data: - for nic in actual['nics']: - assert nic in expected['nics'] - expected['nics'].remove(nic) - assert len(expected['nics']) == 0 - actual['nics'] = [] - assert expected == actual - - def test_free_nodes(self): - api.node_register('master-control-program', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - api.node_register('robocop', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - api.node_register('data', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - result = json.loads(api.list_free_nodes()) - # For the lists to be equal, the ordering must be the same: - result.sort() - assert result == [ - 'data', - 'master-control-program', - 'robocop', - ] - - def test_list_projects(self): - assert json.loads(api.list_projects()) == [] - api.project_create('anvil-nextgen') - assert json.loads(api.list_projects()) == ['anvil-nextgen'] - api.project_create('runway') - api.project_create('manhattan') - assert sorted(json.loads(api.list_projects())) == [ - 'anvil-nextgen', - 'manhattan', - 'runway', - ] - - def test_no_free_nodes(self): - assert json.loads(api.list_free_nodes()) == [] - - def test_some_non_free_nodes(self): - """Make sure that allocated nodes don't show up in the free list.""" - api.node_register('master-control-program', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - api.node_register('robocop', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - api.node_register('data', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - - api.project_create('anvil-nextgen') - api.project_connect_node('anvil-nextgen', 'robocop') - api.project_connect_node('anvil-nextgen', 'data') - - assert json.loads(api.list_free_nodes()) == ['master-control-program'] - - def test_show_node(self): - """Test the show_node api call. - - We create a node, and query it twice: once before it is reserved, - and once after it has been reserved by a project and attached to - a network. Two things should change: (1) "project" should show registered project, - and (2) the newly attached network should be listed. - """ - api.node_register('robocop', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - api.node_register_nic('robocop', 'eth0', 'DE:AD:BE:EF:20:14') - api.node_register_nic('robocop', 'wlan0', 'DE:AD:BE:EF:20:15') - - actual = json.loads(api.show_node('robocop')) - expected = { - 'name': 'robocop', - 'project': None, - 'nics': [ - { - 'label':'eth0', - 'macaddr': 'DE:AD:BE:EF:20:14', - "networks": {} - }, - { - 'label':'wlan0', - 'macaddr': 'DE:AD:BE:EF:20:15', - "networks": {} - } - ], - } - self._compare_node_dumps(actual, expected) - - def test_show_node(self): - """Test the show_node api call. - We create a node, and query it twice: once before it is reserved, - and once after it has been reserved by a project and attached to - a network. Two things should change: (1) "project" should show registered project, - and (2) the newly attached network should be listed. - """ - - def test_show_node_unavailable(self): - api.node_register('robocop', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - api.node_register_nic('robocop', 'eth0', 'DE:AD:BE:EF:20:14') - api.node_register_nic('robocop', 'wlan0', 'DE:AD:BE:EF:20:15') - - actual = json.loads(api.show_node('robocop')) - expected = { - 'name': 'robocop', - 'project': None, - 'nics': [ - { - 'label':'eth0', - 'macaddr': 'DE:AD:BE:EF:20:14', - "networks": {} - }, - { - 'label':'wlan0', - 'macaddr': 'DE:AD:BE:EF:20:15', - "networks": {} - } - ], - } - self._compare_node_dumps(actual, expected) - - def test_show_node_multiple_network(self): - api.node_register('robocop', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - api.node_register_nic('robocop', 'eth0', 'DE:AD:BE:EF:20:14') - api.node_register_nic('robocop', 'wlan0', 'DE:AD:BE:EF:20:15') - - api.project_create('anvil-nextgen') - api.project_connect_node('anvil-nextgen', 'robocop') - network_create_simple('pxe', 'anvil-nextgen') - api.node_connect_network('robocop', 'eth0', 'pxe') - network_create_simple('storage', 'anvil-nextgen') - api.node_connect_network('robocop', 'wlan0', 'storage') - deferred.apply_networking() - - actual = json.loads(api.show_node('robocop')) - expected = { - 'name': 'robocop', - 'project':'anvil-nextgen', - 'nics': [ - { - 'label': 'eth0', - 'macaddr': 'DE:AD:BE:EF:20:14', - "networks": { - get_network_allocator().get_default_channel(): 'pxe' - } - }, - { - 'label': 'wlan0', - 'macaddr': 'DE:AD:BE:EF:20:15', - "networks": { - get_network_allocator().get_default_channel(): 'storage' - } - } - ], - } - self._compare_node_dumps(actual, expected) - - def test_show_nonexistant_node(self): - with pytest.raises(api.NotFoundError): - api.show_node('master-control-program') - - def test_project_nodes_exist(self): - api.node_register('master-control-program', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - api.node_register('robocop', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - api.node_register('data', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - - api.project_create('anvil-nextgen') - api.project_connect_node('anvil-nextgen', 'master-control-program') - api.project_connect_node('anvil-nextgen', 'robocop') - api.project_connect_node('anvil-nextgen', 'data') - result = json.loads(api.list_project_nodes('anvil-nextgen')) - # For the lists to be equal, the ordering must be the same: - result.sort() - assert result == [ - 'data', - 'master-control-program', - 'robocop', - ] - - def test_project_headnodes_exist(self): - api.project_create('anvil-nextgen') - api.headnode_create('hn0', 'anvil-nextgen', 'base-headnode') - api.headnode_create('hn1', 'anvil-nextgen', 'base-headnode') - api.headnode_create('hn2', 'anvil-nextgen', 'base-headnode') - - result = json.loads(api.list_project_headnodes('anvil-nextgen')) - # For the lists to be equal, the ordering must be the same: - result.sort() - assert result == [ - 'hn0', - 'hn1', - 'hn2', - ] - - def test_no_project_nodes(self): - api.project_create('anvil-nextgen') - assert json.loads(api.list_project_nodes('anvil-nextgen')) == [] - - def test_no_project_headnodes(self): - api.project_create('anvil-nextgen') - assert json.loads(api.list_project_headnodes('anvil-nextgen')) == [] - - def test_some_nodes_in_project(self): - """Test that only assigned nodes are in the project.""" - api.node_register('master-control-program', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - api.node_register('robocop', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - api.node_register('data', obm={ - "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", - "host": "ipmihost", - "user": "root", - "password": "tapeworm"}) - - api.project_create('anvil-nextgen') - api.project_connect_node('anvil-nextgen', 'robocop') - api.project_connect_node('anvil-nextgen', 'data') - - result = json.loads(api.list_project_nodes('anvil-nextgen')) - result.sort() - assert result == ['data', 'robocop'] - - def test_project_list_networks(self): - api.project_create('anvil-nextgen') - - network_create_simple('pxe', 'anvil-nextgen') - network_create_simple('public', 'anvil-nextgen') - network_create_simple('private', 'anvil-nextgen') - - result = json.loads(api.list_project_networks('anvil-nextgen')) - # For the lists to be equal, the ordering must be the same: - result.sort() - assert result == [ - 'private', - 'public', - 'pxe' - ] - - def test_no_project_networks(self): - api.project_create('anvil-nextgen') - assert json.loads(api.list_project_nodes('anvil-nextgen')) == [] - - import uuid - - def test_show_headnode(self): - api.project_create('anvil-nextgen') - network_create_simple('spiderwebs', 'anvil-nextgen') - api.headnode_create('BGH', 'anvil-nextgen', 'base-headnode') - api.headnode_create_hnic('BGH', 'eth0') - api.headnode_create_hnic('BGH', 'wlan0') - api.headnode_connect_network('BGH', 'eth0', 'spiderwebs') - - - result = json.loads(api.show_headnode('BGH')) - - # Verify UUID is well formed, then delete it, since we can't match it - # exactly in the check below - temp = uuid.UUID(result['uuid']) - del result['uuid'] - - # For the lists to be equal, the ordering must be the same: - result['hnics'].sort() - assert result == { - 'name': 'BGH', - 'project': 'anvil-nextgen', - 'base_img': 'base-headnode', - 'hnics': [ - 'eth0', - 'wlan0', - ], - 'vncport': None - } - - def test_show_nonexistant_headnode(self): - with pytest.raises(api.NotFoundError): - api.show_headnode('BGH') - - - def test_list_headnode_images(self): - result = json.loads(api.list_headnode_images()) - assert result == [ 'base-headnode', 'img1', 'img2', 'img3', 'img4' ] - - -class Test_show_network: - """Test the show_network api cal.""" - - def test_show_network_simple(self): - api.project_create('anvil-nextgen') - network_create_simple('spiderwebs', 'anvil-nextgen') - - result = json.loads(api.show_network('spiderwebs')) - assert result == { - 'name': 'spiderwebs', - 'creator': 'anvil-nextgen', - 'access': 'anvil-nextgen', - "channels": ["null"] - } - - def test_show_network_public(self): - api.network_create('public-network', - creator='admin', - access='', - net_id='432') - - result = json.loads(api.show_network('public-network')) - assert result == { - 'name': 'public-network', - 'creator': 'admin', - 'channels': ['null'], - } - - def test_show_network_provider(self): - api.project_create('anvil-nextgen') - api.network_create('spiderwebs', - creator='admin', - access='anvil-nextgen', - net_id='451') - - result = json.loads(api.show_network('spiderwebs')) - assert result == { - 'name': 'spiderwebs', - 'creator': 'admin', - 'access': 'anvil-nextgen', - 'channels': ['null'], - } - - - - From 5237d619b7c09745ce93bd6b3b9612bd9470c3f7 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 5 Jul 2016 15:49:24 -0400 Subject: [PATCH 07/71] Addresses unit tests of client Base class --- tests/unit/client.py | 57 ++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index d6473b2b..90a6b629 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -53,10 +53,6 @@ def configure(): config_testsuite() config_merge({ 'extensions': { - 'haas.ext.auth.mock' : '', -# This extension is enabled by default in the tests, so we need to -# disable it explicitly: - 'haas.ext.auth.null': None, 'haas.ext.switches.mock': '', 'haas.ext.obm.ipmi': '', 'haas.ext.obm.mock': '', @@ -88,42 +84,31 @@ def test_auth_db(): assert auth.decode('base64', 'strict') == (":").join([username, password]) -def test_init_error(): - try: - x = ClientBase() - except LookupError: - assert True +class Test_ClientBase: + """ When the username, password is not defined + It should raise a LookupError + """ + + def test_init_error(self): + try: + x = ClientBase() + except LookupError: + assert True + + def test_correct_init(self): + x = ClientBase(ep, 'some_base64_string') + assert x.endpoint == "http://127.0.0.1" + assert x.auth == "some_base64_string" + + def test_object_url(self): + x = ClientBase(ep, 'some_base64_string') + y = x.object_url('abc', '123', 'xy23z') + assert y == 'http://127.0.0.1/abc/123/xy23z' + -def test_correct_init(): - x = ClientBase(ep, 'some_base64_string') - assert x.endpoint == "http://127.0.0.1" - assert x.auth == "some_base64_string" -def test_object_url(): - x = ClientBase(ep, 'some_base64_string') - y = x.object_url('abc', '123', 'xy23z') - assert y == 'http://127.0.0.1/abc/123/xy23z' -#class TestNodeOperations: -# """Tests for the haas.client.node.* operations. """ -# -# def test_list_free_nodes(self): -# api.node_register('master-control-program', obm={ -# "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", -# "host": "ipmihost", -# "user": "root", -# "password": "tapeworm"}) -# api.node_register('robocop', obm={ -# "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", -# "host": "ipmihost", -# "user": "root", -# "password": "tapeworm"}) -# api.node_register('data', obm={ -# "type": "http://schema.massopencloud.org/haas/v0/obm/ipmi", -# "host": "ipmihost", -# "user": "root", -# "password": "tapeworm"}) From cae9dd49c78c3d794882b68a52feb387a6b4ed6d Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 5 Jul 2016 16:17:46 -0400 Subject: [PATCH 08/71] Supports new call list_nodes in place of list_free_nodes. --- haas/cli.py | 18 ++++++++++++++---- haas/client/node.py | 6 ++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index b06e6f85..df782c6d 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -499,10 +499,20 @@ def list_nodes(is_free): may be either "all" or "free", and determines whether to list all nodes or all free nodes. """ - if is_free not in ('all', 'free'): - raise TypeError("is_free must be either 'all' or 'free'") - url = object_url('node', is_free) - do_get(url) + q = C.node.list(is_free) + if is_free == 'all': + sys.stdout.write('All nodes {}\t: {}\n'.format(len(q), " ".join(q))) + elif is_free == 'free': + sys.stdout.write('Free nodes {}\t: {}\n'.format(len(q), " ".join(q))) + else: + sys.stdout.write('Error: {} is an invalid argument\n'.format(is_free)) + + + +# if is_free not in ('all', 'free'): +# raise TypeError("is_free must be either 'all' or 'free'") +# url = object_url('node', is_free) +# do_get(url) @cmd def list_project_nodes(project): diff --git a/haas/client/node.py b/haas/client/node.py index ec501315..55a3d83b 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -6,10 +6,9 @@ class Node(ClientBase): """ Consists of calls to query and manipulate node related objects and relations """ - def free_list(self): + def list(self, is_free): """ List all nodes that HIL manages """ - url = self.object_url('/free_nodes') - print url + url = self.object_url('node', is_free) q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) if q.ok: return q.json() @@ -21,7 +20,6 @@ def show_node(self, node_name): self.node_name = node_name url = self.object_url('node', node_name) - print url q = requests.get(url, headers={"Authorization": "Basic %s" %self.auth}) return q.json() From 06b197adc3b10f3c7d77b4ca66f28bf5257b0802 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 25 Jul 2016 13:24:21 -0400 Subject: [PATCH 09/71] adds __init__ file --- haas/client/__init__.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 haas/client/__init__.py diff --git a/haas/client/__init__.py b/haas/client/__init__.py new file mode 100644 index 00000000..b28b04f6 --- /dev/null +++ b/haas/client/__init__.py @@ -0,0 +1,3 @@ + + + From f9eb580e1804db5d072f2d178d40b016460c1ef9 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 26 Jul 2016 10:01:03 -0400 Subject: [PATCH 10/71] making unit tests to work --- tests/unit/client.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 90a6b629..5d19bcc7 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -29,7 +29,7 @@ #from haas.client_lib.client_lib import hilClientLib ## Hook to the client library from haas.client.base import ClientBase -from haas.client.auth import * +from haas.client.auth import auth_db from haas.client.client import Client @@ -53,6 +53,10 @@ def configure(): config_testsuite() config_merge({ 'extensions': { +# This extension is enabled by default in the tests, so we need to +# disable it explicitly: + 'haas.ext.auth.null': None, +# 'haas.ext.auth.mock': '', 'haas.ext.switches.mock': '', 'haas.ext.obm.ipmi': '', 'haas.ext.obm.mock': '', @@ -106,6 +110,32 @@ def test_object_url(self): assert y == 'http://127.0.0.1/abc/123/xy23z' +class Test_queryProject: + """ Will test the query call list_projects.""" + + def test_list_projects(self): + api.project_create('proj1') + api.project_create('proj2') + api.project_create('proj3') + assert C.project.list() == "3 Projects : proj1 proj2 proj3" + + + + + + + + + + + + + + + + + + From 2e63285744210df25f220113210e2e411876a6d9 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 8 Aug 2016 04:51:54 -0400 Subject: [PATCH 11/71] Includes a clean way to 1. Configure haas.cfg 2. Instantiate the database 3. Populate the database using dummy objects via restful requests 4. Tear down the setup in a clean fashion. --- tests/unit/client.py | 185 +++++++++++++++++++++++++++++++++---------- 1 file changed, 143 insertions(+), 42 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 5d19bcc7..13184442 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -12,7 +12,7 @@ # express or implied. See the License for the specific language # governing permissions and limitations under the License. -"""Unit tests for api.py""" +"""Unit tests for client library""" import haas from haas import model, api, deferred, server, config from haas.model import db @@ -23,8 +23,11 @@ import requests import os +import tempfile import subprocess +from subprocess import check_call, Popen from urlparse import urljoin +import requests from requests.exceptions import ConnectionError #from haas.client_lib.client_lib import hilClientLib ## Hook to the client library @@ -34,6 +37,7 @@ + MOCK_SWITCH_TYPE = 'http://schema.massopencloud.org/haas/v0/switches/mock' OBM_TYPE_MOCK = 'http://schema.massopencloud.org/haas/v0/obm/mock' OBM_TYPE_IPMI = 'http://schema.massopencloud.org/haas/v0/obm/ipmi' @@ -48,40 +52,6 @@ C = Client(ep, auth) #Initializing client library -@pytest.fixture -def configure(): - config_testsuite() - config_merge({ - 'extensions': { -# This extension is enabled by default in the tests, so we need to -# disable it explicitly: - 'haas.ext.auth.null': None, -# 'haas.ext.auth.mock': '', - 'haas.ext.switches.mock': '', - 'haas.ext.obm.ipmi': '', - 'haas.ext.obm.mock': '', - }, - }) - config.load_extensions() - - -fresh_database = pytest.fixture(fresh_database) - - -@pytest.fixture -def server_init(): - server.register_drivers() - server.validate_state() - - -with_request_context = pytest.yield_fixture(with_request_context) - - -pytestmark = pytest.mark.usefixtures('configure', - 'fresh_database', - 'server_init', - 'with_request_context') - def test_auth_db(): auth = auth_db(username, password) @@ -109,31 +79,162 @@ def test_object_url(self): y = x.object_url('abc', '123', 'xy23z') assert y == 'http://127.0.0.1/abc/123/xy23z' +#For testing the client library we need a running HIL server, with dummy +#objects populated. Following classes accomplish that end. +# It shall: +# 1. Configures haas.cfg +# 2. Instantiates a database +# 3. Starts a server on an arbitary port +# 4. Populates haas with dummy objects +# 5. tears down the setup in a clean fashion. + + +@pytest.fixture(autouse=True) +def make_config(): + tmpdir = tempfile.mkdtemp() + cwd = os.getcwd() + os.chdir(tmpdir) + with open('haas.cfg', 'w') as f: + # We need to make sure the database ends up in the tmpdir directory, + # and Flask-SQLAlchemy doesn't seem to want to do relative paths, so + # we can't just do a big string literal. + config = '\n'.join([ + '[general]' + 'log_level = debug', + + '[auth]', + 'require_authentication = False', + + '[headnode]', + 'base_imgs = base-headnode, img1, img2, img3, img4', + '[database]', + 'uri = sqlite:///%s/haas.db' % tmpdir, + '[extensions]', + 'haas.ext.auth.null =', + 'haas.ext.network_allocators.null =', + '[extensions]', + 'haas.ext.switches.mock =', + 'haas.ext.auth.null =', + 'haas.ext.switches.nexus =', + 'haas.ext.switches.dell =', + 'haas.ext.switches.brocade =', + + 'haas.ext.obm.ipmi =', + 'haas.ext.network_allocators.null =', + '[haas.ext.network_allocators.vlan_pool]', + 'vlans = 1001-1040', + + ]) + f.write(config) + return (tmpdir, cwd) + +def cleanup((tmpdir, cwd)): + + os.remove('haas.cfg') + os.remove('haas.db') + os.chdir(cwd) + os.rmdir(tmpdir) + +#request.addfinalizer(cleanup) + + +def db_create(): + check_call(['haas-admin', 'db', 'create']) + + +def run_server(cmd): + """This function starts a haas server. + The arguments in 'cmd' will be a list of arguments like required to start a + haas server like ['haas', 'serve', '8888'] + It will return a handle which can be used to terminate the server when + tests finish. + """ + proc = Popen(cmd) + return proc + +def populate_objects(): + """ + Once the server is started, this function will populate some mock objects + to faciliate testing of the client library + """ + + ## Adding nodes + url_node = 'http://127.0.0.1:8888/node/' + api_nodename='http://schema.massopencloud.org/haas/v0/obm/' -class Test_queryProject: - """ Will test the query call list_projects.""" + obminfo1 = {"type": api_nodename+'ipmi', "host":"10.10.0.01", + "user":"ipmi_u", "password":"pass1234" } - def test_list_projects(self): - api.project_create('proj1') - api.project_create('proj2') - api.project_create('proj3') - assert C.project.list() == "3 Projects : proj1 proj2 proj3" + obminfo2 = {"type": api_nodename+'ipmi', "host":"10.10.0.02", + "user":"ipmi_u", "password":"pass1234" } + obminfo3 = {"type": api_nodename+'ipmi', "host":"10.10.0.03", + "user":"ipmi_u", "password":"pass1234" } + obminfo4 = {"type": api_nodename+'ipmi', "host":"10.10.0.04", + "user":"ipmi_u", "password":"pass1234" } + obminfo5 = {"type": api_nodename+'ipmi', "host":"10.10.0.05", + "user":"ipmi_u", "password":"pass1234" } + obminfo6 = {"type": api_nodename+'ipmi', "host":"10.10.0.06", + "user":"ipmi_u", "password":"pass1234" } + requests.put(url_node+'node-01', data=json.dumps({"obm":obminfo1})) + requests.put(url_node+'node-02', data=json.dumps({"obm":obminfo2})) + requests.put(url_node+'node-03', data=json.dumps({"obm":obminfo3})) + requests.put(url_node+'node-04', data=json.dumps({"obm":obminfo4})) + requests.put(url_node+'node-05', data=json.dumps({"obm":obminfo5})) + requests.put(url_node+'node-06', data=json.dumps({"obm":obminfo6})) + ## Adding Projects + for i in [ "proj-01", "proj-02", "proj-03" ]: + requests.put('http://127.0.0.1:8888/project/'+i) + ## Adding switches + url='http://127.0.0.1:8888/switch/' + api_name='http://schema.massopencloud.org/haas/v0/switches/' + dell_param={ 'type':api_name+'powerconnect55xx', 'hostname':'dell-01', + 'username':'root', 'password':'root1234' } + nexus_param={ 'type':api_name+'nexus', 'hostname':'nexus-01', + 'username':'root', 'password':'root1234', 'dummy_vlan':'333' } + mock_param={ 'type':api_name+'mock', 'hostname':'mockSwitch-01', + 'username':'root', 'password':'root1234' } + brocade_param={ 'type':api_name+'brocade', 'hostname':'brocade-01', + 'username':'root', 'password':'root1234', 'interface_type': + 'TenGigabitEthernet' } + requests.put(url+'dell-01', data=json.dumps(dell_param)) + requests.put(url+'nexus-01', data=json.dumps(nexus_param)) + requests.put(url+'mock-01', data=json.dumps(mock_param)) + requests.put(url+'brocade-01', data=json.dumps(brocade_param)) + ## Adding nodes to projects + url_project = 'http://127.0.0.1:8888/project/' + # Adding nodes 1 to proj-01 + requests.post( url_project+'proj-01'+'/connect_node', data=json.dumps({'node':'node-01'})) + # Adding nodes 2, 4 to proj-02 + requests.post( url_project+'proj-02'+'/connect_node', data=json.dumps({'node':'node-02'})) + requests.post( url_project+'proj-02'+'/connect_node', data=json.dumps({'node':'node-04'})) + # Adding node 3, 5 to proj-03 + requests.post( url_project+'proj-03'+'/connect_node', data=json.dumps({'node':'node-03'})) + requests.post( url_project+'proj-03'+'/connect_node', data=json.dumps({'node':'node-05'})) + ## Adding networks to projects + url_network='http://127.0.0.1:8888/network/' + for i in [ 'net-01', 'net-02', 'net-03' ]: + requests.put(url_network+i, data=json.dumps({"creator":"proj-01", + "access": "proj-01", "net_id": ""})) +#tmpdir = make_config() +#db_create() +#proc = run_server(['haas', 'serve', '8888' ]) +#cleanup(tmpdir) From f36ac1fe5593590bec56d864bea5c9237e8e86f9 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 8 Aug 2016 13:10:45 -0400 Subject: [PATCH 12/71] unit tests pass. --- tests/unit/client.py | 93 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 19 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 13184442..f5864b0d 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -42,7 +42,7 @@ OBM_TYPE_MOCK = 'http://schema.massopencloud.org/haas/v0/obm/mock' OBM_TYPE_IPMI = 'http://schema.massopencloud.org/haas/v0/obm/ipmi' -ep = "http://127.0.0.1" or os.environ.get('HAAS_ENDPOINT') +ep = "http://127.0.0.1:8888" or os.environ.get('HAAS_ENDPOINT') username = "hil_user" or os.environ.get('HAAS_USERNAME') password = "hil_pass1234" or os.environ.get('HAAS_PASSWORD') @@ -52,6 +52,7 @@ C = Client(ep, auth) #Initializing client library +## Following tests check if the client library is initialized correctly def test_auth_db(): auth = auth_db(username, password) @@ -71,13 +72,13 @@ def test_init_error(self): def test_correct_init(self): x = ClientBase(ep, 'some_base64_string') - assert x.endpoint == "http://127.0.0.1" + assert x.endpoint == "http://127.0.0.1:8888" assert x.auth == "some_base64_string" def test_object_url(self): x = ClientBase(ep, 'some_base64_string') y = x.object_url('abc', '123', 'xy23z') - assert y == 'http://127.0.0.1/abc/123/xy23z' + assert y == 'http://127.0.0.1:8888/abc/123/xy23z' #For testing the client library we need a running HIL server, with dummy #objects populated. Following classes accomplish that end. @@ -89,15 +90,17 @@ def test_object_url(self): # 5. tears down the setup in a clean fashion. -@pytest.fixture(autouse=True) +#pytest.fixture(scope="module") + def make_config(): + """ This function creates haas.cfg with desired options + and writes to a temporary directory. + It returns a tuple where (tmpdir, cwd) = ('location of haas.cfg', 'pwdd') + """ tmpdir = tempfile.mkdtemp() cwd = os.getcwd() os.chdir(tmpdir) with open('haas.cfg', 'w') as f: - # We need to make sure the database ends up in the tmpdir directory, - # and Flask-SQLAlchemy doesn't seem to want to do relative paths, so - # we can't just do a big string literal. config = '\n'.join([ '[general]' 'log_level = debug', @@ -128,17 +131,22 @@ def make_config(): f.write(config) return (tmpdir, cwd) + def cleanup((tmpdir, cwd)): + """ Cleanup crew, when all tests are done. + It will shutdown the haas server, + delete any files and folders created for the tests. + """ os.remove('haas.cfg') os.remove('haas.db') os.chdir(cwd) os.rmdir(tmpdir) -#request.addfinalizer(cleanup) -def db_create(): +def initialize_db(): + """ Creates an database as defined in haas.cfg.""" check_call(['haas-admin', 'db', 'create']) @@ -152,13 +160,13 @@ def run_server(cmd): proc = Popen(cmd) return proc -def populate_objects(): +def populate_server(): """ Once the server is started, this function will populate some mock objects to faciliate testing of the client library """ - ## Adding nodes + ## Adding nodes, node-01 - node-06 url_node = 'http://127.0.0.1:8888/node/' api_nodename='http://schema.massopencloud.org/haas/v0/obm/' @@ -189,11 +197,11 @@ def populate_objects(): requests.put(url_node+'node-05', data=json.dumps({"obm":obminfo5})) requests.put(url_node+'node-06', data=json.dumps({"obm":obminfo6})) - ## Adding Projects + ## Adding Projects proj-01 - proj-03 for i in [ "proj-01", "proj-02", "proj-03" ]: requests.put('http://127.0.0.1:8888/project/'+i) - ## Adding switches + ## Adding switches one for each driver url='http://127.0.0.1:8888/switch/' api_name='http://schema.massopencloud.org/haas/v0/switches/' @@ -212,7 +220,7 @@ def populate_objects(): requests.put(url+'mock-01', data=json.dumps(mock_param)) requests.put(url+'brocade-01', data=json.dumps(brocade_param)) - ## Adding nodes to projects + ## Allocating nodes to projects url_project = 'http://127.0.0.1:8888/project/' # Adding nodes 1 to proj-01 requests.post( url_project+'proj-01'+'/connect_node', data=json.dumps({'node':'node-01'})) @@ -223,23 +231,70 @@ def populate_objects(): requests.post( url_project+'proj-03'+'/connect_node', data=json.dumps({'node':'node-03'})) requests.post( url_project+'proj-03'+'/connect_node', data=json.dumps({'node':'node-05'})) - ## Adding networks to projects + ## Assigning networks to projects url_network='http://127.0.0.1:8888/network/' for i in [ 'net-01', 'net-02', 'net-03' ]: requests.put(url_network+i, data=json.dumps({"creator":"proj-01", "access": "proj-01", "net_id": ""})) -#tmpdir = make_config() -#db_create() -#proc = run_server(['haas', 'serve', '8888' ]) + # -- SETUP -- # +@pytest.fixture(scope="session") +def create_setup(request): + dir_names = make_config() + initialize_db() + proc = run_server(['haas', 'serve', '8888' ]) + populate_server() + + def fin(): + print "stopping the server, cleaning the files " + proc.terminate() + cleanup(dir_names) + request.addfinalizer(fin) + + + + + +@pytest.mark.usefixtures("create_setup") +class Test_Node: + """ Tests Node related client calls. """ + + def test_list_nodes_free(self): + result = C.node.list('free') + assert result == [u'node-06'] + + def test_list_nodes_all(self): + result = C.node.list('all') + assert result == [u'node-01', u'node-02', u'node-03', u'node-04', + u'node-05', u'node-06'] -#cleanup(tmpdir) +@pytest.mark.usefixtures("create_setup") +class Test_project: + """ Tests project related client calls.""" + def test_list_projects(self): + result = C.project.list() + assert result == [u'proj-01', u'proj-02', u'proj-03'] + def test_list_nodes_inproject(self): + result01 = C.project.nodes_in('proj-01') + result02 = C.project.nodes_in('proj-02') + assert result01 == [u'node-01'] + assert result02 == [u'node-02', u'node-04'] + def test_list_networks_inproject(self): + result = C.project.networks_in('proj-01') + assert result == [u'net-01', u'net-02', u'net-03'] +@pytest.mark.usefixtures("create_setup") +class Test_switch: + """ Tests switch related client calls.""" + def test_list_switches(self): + result = C.switch.list() + assert result == [u'brocade-01', u'dell-01', u'mock-01', u'nexus-01'] +## End of tests ## From 595c30dded893d719eeb3a7556835af266b46750 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 8 Aug 2016 16:15:59 -0400 Subject: [PATCH 13/71] Fixes unit tests --- tests/unit/client.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index f5864b0d..bc76931f 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -72,13 +72,13 @@ def test_init_error(self): def test_correct_init(self): x = ClientBase(ep, 'some_base64_string') - assert x.endpoint == "http://127.0.0.1:8888" + assert x.endpoint == "http://127.0.0.1:8888" assert x.auth == "some_base64_string" def test_object_url(self): x = ClientBase(ep, 'some_base64_string') y = x.object_url('abc', '123', 'xy23z') - assert y == 'http://127.0.0.1:8888/abc/123/xy23z' + assert y == 'http://127.0.0.1:8888/abc/123/xy23z' #For testing the client library we need a running HIL server, with dummy #objects populated. Following classes accomplish that end. @@ -244,10 +244,11 @@ def create_setup(request): dir_names = make_config() initialize_db() proc = run_server(['haas', 'serve', '8888' ]) + import time + time.sleep(0.5) populate_server() def fin(): - print "stopping the server, cleaning the files " proc.terminate() cleanup(dir_names) request.addfinalizer(fin) From b0106acbdf0eb975dd68176ca590c031154aaf4b Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 8 Aug 2016 16:27:32 -0400 Subject: [PATCH 14/71] increasing delay to fix tests --- tests/unit/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index bc76931f..861b12b5 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -134,7 +134,7 @@ def make_config(): def cleanup((tmpdir, cwd)): """ Cleanup crew, when all tests are done. - It will shutdown the haas server, + It will shutdown the haas server, delete any files and folders created for the tests. """ @@ -245,7 +245,7 @@ def create_setup(request): initialize_db() proc = run_server(['haas', 'serve', '8888' ]) import time - time.sleep(0.5) + time.sleep(1) populate_server() def fin(): From 27a619faf6fe7d73a194b726087ced1c88b0fee0 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 10 Aug 2016 15:29:27 -0400 Subject: [PATCH 15/71] fixes CI test --- haas/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/haas/cli.py b/haas/cli.py index 92c7a2da..cee8a3bb 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -32,7 +32,7 @@ from haas.client.client import Client -ep = "http://127.0.0.1:5000" or os.environ.get('HAAS_ENDPOINT') +ep = os.environ.get('HAAS_ENDPOINT') or "http://127.0.0.1:5000" username = "jil" or os.environ.get('HAAS_USERNAME') password = "tumbling" or os.environ.get('HAAS_PASSWORD') From e04d62906ce016d99cd38fd4ad8ed540c4cfd412 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 10 Aug 2016 16:15:48 -0400 Subject: [PATCH 16/71] fixing CI tests --- tests/integration/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/integration/cli.py b/tests/integration/cli.py index 8cd20822..837f1252 100644 --- a/tests/integration/cli.py +++ b/tests/integration/cli.py @@ -10,21 +10,21 @@ def test_add_list_delete_projects(): The project list does not need to be empty before running the test.""" output = subprocess.check_output(['haas', 'list_projects']) - projects = json.loads(output) + projects = output.split(" ") assert PROJECT1 not in projects assert PROJECT2 not in projects size = len(projects) subprocess.check_call(['haas', 'project_create', PROJECT1]) output = subprocess.check_output(['haas', 'list_projects']) - projects = json.loads(output) + projects = output.split(" ") assert PROJECT1 in projects assert PROJECT2 not in projects assert len(projects) == size + 1 subprocess.check_call(['haas', 'project_delete', PROJECT1]) output = subprocess.check_output(['haas', 'list_projects']) - projects = json.loads(output) + projects = output.split(" ") assert PROJECT1 not in projects assert PROJECT2 not in projects assert len(projects) == size From 7cddc95f5c0cee57e34c6fe7631b945f8c3ddd5b Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 10 Oct 2016 23:43:48 -0400 Subject: [PATCH 17/71] Fixes call list_nodes in the client library. Had the same issue as fixed in #635 --- haas/client/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/haas/client/node.py b/haas/client/node.py index 55a3d83b..f580c2ca 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -8,7 +8,7 @@ class Node(ClientBase): def list(self, is_free): """ List all nodes that HIL manages """ - url = self.object_url('node', is_free) + url = self.object_url('nodes', is_free) q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) if q.ok: return q.json() From ef94c611a44368f10b894f1ea2ece5dd732aabe9 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 11 Oct 2016 04:54:52 -0400 Subject: [PATCH 18/71] Adding add/delete operations and tests related to project --- haas/cli.py | 25 +++++++++---------------- haas/client/client.py | 4 ++-- haas/client/client_errors.py | 2 ++ haas/client/project.py | 34 ++++++++++++++++++++++++++++++++++ tests/unit/client.py | 26 ++++++++++++++++++++------ 5 files changed, 67 insertions(+), 24 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 15dfe13e..b3ede1b5 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -30,6 +30,7 @@ ## Hook to the client library from haas.client.auth import * from haas.client.client import Client +from haas.client.client_errors import * ep = os.environ.get('HAAS_ENDPOINT') or "http://127.0.0.1:5000" @@ -382,27 +383,27 @@ def user_add_project(user, project): url = object_url('/auth/basic/user', user, 'add_project') do_post(url, data={'project': project}) - @cmd def user_remove_project(user, project): """Remove from """ url = object_url('/auth/basic/user', user, 'remove_project') do_post(url, data={'project': project}) - @cmd def project_create(project): """Create a """ - url = object_url('project', project) - do_put(url) - + try: + C.project.create(project) + except (DuplicateError, AuthenticationError) as e: + print e @cmd def project_delete(project): """Delete """ - url = object_url('project', project) - do_delete(url) - + try: + C.project.delete(project) + except (NotFoundError, AuthenticationError) as e: + print e @cmd def headnode_create(headnode, project, base_img): @@ -411,7 +412,6 @@ def headnode_create(headnode, project, base_img): do_put(url, data={'project': project, 'base_img': base_img}) - @cmd def headnode_delete(headnode): """Delete """ @@ -687,19 +687,12 @@ def list_project_nodes(project): """List all nodes attached to a """ q = C.project.nodes_in(project) sys.stdout.write('Nodes allocated to %s: ' %project + " ".join(q) + '\n') -# print q - -# url = object_url('project', project, 'nodes') -# do_get(url) - @cmd def list_project_networks(project): """List all networks attached to a """ q = C.project.networks_in(project) sys.stdout.write("Networks allocated to {}\t: {}\n".format(project, " ".join(q))) -# url = object_url('project', project, 'networks') -# do_get(url) @cmd diff --git a/haas/client/client.py b/haas/client/client.py index 0a2ae01a..de8a0d50 100644 --- a/haas/client/client.py +++ b/haas/client/client.py @@ -2,7 +2,7 @@ from haas.client.node import Node from haas.client.project import Project from haas.client.switch import Switch -#from haas.client.network import Network +from haas.client.network import Network from haas.client.auth import auth_db @@ -15,5 +15,5 @@ def __init__(self, endpoint, auth): self.node = Node(self.endpoint, self.auth) self.project = Project(self.endpoint, self.auth) self.switch = Switch(self.endpoint, self.auth) -# self.network = Network(self.endpoint, self.auth) + self.network = Network(self.endpoint, self.auth) diff --git a/haas/client/client_errors.py b/haas/client/client_errors.py index 2fc63a87..28807270 100644 --- a/haas/client/client_errors.py +++ b/haas/client/client_errors.py @@ -4,3 +4,5 @@ class AuthenticationError(Exception): class NotFoundError(Exception): pass +class DuplicateError(Exception): + pass diff --git a/haas/client/project.py b/haas/client/project.py index 8e7ef784..f5325725 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -40,4 +40,38 @@ def networks_in(self, project_name): elif q.status_code == 404: raise NotFoundError("Project %s does not exist." % self.project_name) + def create(self, project_name): + """ Creates a project named """ + self.project_name = project_name + url = self.object_url('project', self.project_name) + q = requests.put(url, headers={"Authorization": "Basic %s" % self.auth}) + if q.ok: + return + elif q.status_code == 401: + raise AuthenticationError("Invalid credentials.") + elif q.status_code == 409: + raise DuplicateError("Project Name not unique.") + + def delete(self, project_name): + """ Deletes a project named """ + self.project_name = project_name + url = self.object_url('project', self.project_name) + q = requests.delete(url, headers={"Authorization": "Basic %s" % self.auth}) + if q.ok: + return + elif q.status_code == 401: + raise AuthenticationError("Invalid credentials.") + elif q.status_code == 404: + raise NotFoundError("Deletion failed. Project does not exist.") + +# def add_node(self, project_name, node_name): +# """ Adds a node to a project. """ +# self.project_name = project_name +# self.node_name = node_name +# +# url = self.object_url('project', project_name, 'connect_node') + + + + diff --git a/tests/unit/client.py b/tests/unit/client.py index 861b12b5..7bb1f712 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -34,7 +34,7 @@ from haas.client.base import ClientBase from haas.client.auth import auth_db from haas.client.client import Client - +from haas.client import client_errors @@ -253,10 +253,6 @@ def fin(): cleanup(dir_names) request.addfinalizer(fin) - - - - @pytest.mark.usefixtures("create_setup") class Test_Node: """ Tests Node related client calls. """ @@ -270,7 +266,6 @@ def test_list_nodes_all(self): assert result == [u'node-01', u'node-02', u'node-03', u'node-04', u'node-05', u'node-06'] - @pytest.mark.usefixtures("create_setup") class Test_project: """ Tests project related client calls.""" @@ -289,6 +284,25 @@ def test_list_networks_inproject(self): result = C.project.networks_in('proj-01') assert result == [u'net-01', u'net-02', u'net-03'] + def test_project_create(self): + result = C.project.create('dummy-01') + assert result == None + + def test_duplicate_project_create(self): + C.project.create('dummy-01') + with pytest.raises(client_errors.DuplicateError): + C.project.create('dummy-01') + + def test_project_delete(self): + C.project.create('dummy-02') + result = C.project.delete('dummy-02') + assert result == None + + def test_error_project_delete(self): + with pytest.raises(client_errors.NotFoundError): + C.project.delete('dummy-03') + + @pytest.mark.usefixtures("create_setup") class Test_switch: """ Tests switch related client calls.""" From e5f31503d26f388e5da2e338527806be08d7292f Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 11 Oct 2016 11:47:55 -0400 Subject: [PATCH 19/71] Adding network module to client library --- haas/client/network.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 haas/client/network.py diff --git a/haas/client/network.py b/haas/client/network.py new file mode 100644 index 00000000..58091099 --- /dev/null +++ b/haas/client/network.py @@ -0,0 +1,21 @@ +import requests +import json + +from haas.client.base import ClientBase +from haas.client.client_errors import * + + +class Network(ClientBase): + """ Consists of calls to query and manipulate project related + objects and relations """ + + def list(self): + """ Lists all projects under HIL """ + url = self.object_url('/networks') + q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + if q.ok: + return q.json() + elif q.status_code == 401: + raise AuthenticationError("Make sure credentials match chosen authentication backend.") + + From c07cc78547f334b37e4d5cc170c5b30c4d7a4779 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 12 Oct 2016 06:27:37 -0400 Subject: [PATCH 20/71] Adds client library support and unit tests for -- project_connect_node -- project_detach_node Unit test_project_disconnect_node is taking almost 30s, just fyi. --- haas/cli.py | 19 ++++++++++--------- haas/client/project.py | 36 ++++++++++++++++++++++++++++++------ tests/unit/client.py | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 15 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index b3ede1b5..d8ae4c44 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -395,7 +395,7 @@ def project_create(project): try: C.project.create(project) except (DuplicateError, AuthenticationError) as e: - print e + sys.stderr.write('Error: %s\n' % e.message) @cmd def project_delete(project): @@ -403,7 +403,7 @@ def project_delete(project): try: C.project.delete(project) except (NotFoundError, AuthenticationError) as e: - print e + sys.stderr.write('Error: %s\n' % e.message) @cmd def headnode_create(headnode, project, base_img): @@ -422,15 +422,18 @@ def headnode_delete(headnode): @cmd def project_connect_node(project, node): """Connect to """ - url = object_url('project', project, 'connect_node') - do_post(url, data={'node': node}) - + try: + C.project.connect(project, node) + except (NotFoundError, DuplicateError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd def project_detach_node(project, node): """Detach from """ - url = object_url('project', project, 'detach_node') - do_post(url, data={'node': node}) + try: + C.project.disconnect(project, node) + except (NotFoundError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -707,8 +710,6 @@ def show_network(network): """Display information about """ q = C.project.networks_in(project) sys.stdout.write("Networks allocated to {}\t: {}\n".format(project, " ".join(q))) -# url = object_url('network', network) -# do_get(url) @cmd diff --git a/haas/client/project.py b/haas/client/project.py index f5325725..f35d4d1f 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -64,12 +64,36 @@ def delete(self, project_name): elif q.status_code == 404: raise NotFoundError("Deletion failed. Project does not exist.") -# def add_node(self, project_name, node_name): -# """ Adds a node to a project. """ -# self.project_name = project_name -# self.node_name = node_name -# -# url = self.object_url('project', project_name, 'connect_node') + def connect(self, project_name, node_name): + """ Adds a node to a project. """ + self.project_name = project_name + self.node_name = node_name + + url = self.object_url('project', project_name, 'connect_node') + payload=json.dumps({'node':node_name}) + q = requests.post(url, headers={"Authorization": "Basic %s" % self.auth}, data=payload) + if q.ok: + return + elif q.status_code == 409: + raise DuplicateError("Node is already owned by the project. ") + elif q.status_code == 404: + raise NotFoundError("Node or project does not exist. ") + + def disconnect(self, project_name, node_name): + """ Adds a node to a project. """ + self.project_name = project_name + self.node_name = node_name + + url = self.object_url('project', project_name, 'detach_node') + payload=json.dumps({'node':node_name}) + q = requests.post(url, headers={"Authorization": "Basic %s" % self.auth}, data=payload) + if q.ok: + return + elif q.status_code == 404: + raise NotFoundError("Node or Project does not exist or Node not owned by project") + + + diff --git a/tests/unit/client.py b/tests/unit/client.py index 7bb1f712..985b18d2 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -271,37 +271,78 @@ class Test_project: """ Tests project related client calls.""" def test_list_projects(self): + """ test for getting list of project """ result = C.project.list() assert result == [u'proj-01', u'proj-02', u'proj-03'] def test_list_nodes_inproject(self): + """ test for getting list of nodes connected to a project. """ result01 = C.project.nodes_in('proj-01') result02 = C.project.nodes_in('proj-02') assert result01 == [u'node-01'] assert result02 == [u'node-02', u'node-04'] def test_list_networks_inproject(self): + """ test for getting list of networks connected to a project. """ result = C.project.networks_in('proj-01') assert result == [u'net-01', u'net-02', u'net-03'] def test_project_create(self): + """ test for creating project. """ result = C.project.create('dummy-01') assert result == None def test_duplicate_project_create(self): + """ test for catching duplicate name while creating new project. """ C.project.create('dummy-01') with pytest.raises(client_errors.DuplicateError): C.project.create('dummy-01') def test_project_delete(self): + """ test for deleting project. """ C.project.create('dummy-02') result = C.project.delete('dummy-02') assert result == None def test_error_project_delete(self): + """ test to capture error condition in project delete. """ with pytest.raises(client_errors.NotFoundError): C.project.delete('dummy-03') + def test_project_connect_node(self): + """ test for connecting node to project. """ + C.project.create('abcd') + result = C.project.connect('abcd', 'node-06') + assert result == None + + def test_project_connect_node_duplicate(self): + """ test for erronous reconnecting node to project. """ + C.project.create('abcd') + C.project.connect('abcd', 'node-06') + with pytest.raises(client_errors.DuplicateError): + C.project.connect('abcd', 'node-06') + + def test_project_connect_node_nosuchobject(self): + """ test for connecting no such node or project """ + C.project.create('abcd') + with pytest.raises(client_errors.NotFoundError): + C.project.connect('abcd', 'no-such-node') + with pytest.raises(client_errors.NotFoundError): + C.project.connect('no-such-project', 'node-06') + + def test_project_disconnect_node(self): + """ Test for correctly disconnecting node from project.""" + result = C.project.disconnect('proj-01', 'node-01') + assert result == None + # ?? This tests takes same time as sum of all the other tests ?? + + def test_project_disconnect_node_nosuchobject(self): + """ Test for errors while disconnecting node from project.""" + C.project.create('abcd') + with pytest.raises(client_errors.NotFoundError): + C.project.disconnect('abcd', 'no-such-node') + with pytest.raises(client_errors.NotFoundError): + C.project.disconnect('no-such-project', 'node-06') @pytest.mark.usefixtures("create_setup") class Test_switch: From 16de1047fb90a2de0f5cd2d0424655e81b14901b Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 17 Oct 2016 01:35:31 -0400 Subject: [PATCH 21/71] Adds node operations. --- haas/cli.py | 40 ++++++++++++++----- haas/client/client_errors.py | 3 ++ haas/client/node.py | 76 ++++++++++++++++++++++++++++++++++++ tests/unit/client.py | 12 +++--- 4 files changed, 114 insertions(+), 17 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index d8ae4c44..d98d0b2c 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -484,22 +484,35 @@ def node_register(node, subtype, *args): @cmd def node_delete(node): """Delete """ - url = object_url('node', node) - do_delete(url) +# import pdb; pdb.set_trace() + try: + C.node.delete(node) + except (NotFoundError, BlockedError) as e: + sys.stderr.write('Error: %s\n' % e.message) + +# url = object_url('node', node) +# do_delete(url) @cmd def node_power_cycle(node): """Power cycle """ - url = object_url('node', node, 'power_cycle') - do_post(url) + try: + C.node.power_cycle(node) + except (NotFoundError, BlockedError) as e: + sys.stderr.write('Error: %s\n' % e.message) + +# url = object_url('node', node, 'power_cycle') +# do_post(url) @cmd def node_power_off(node): """Power off """ - url = object_url('node', node, 'power_off') - do_post(url) + try: + C.node.power_off(node) + except (NotFoundError, BlockedError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -507,15 +520,22 @@ def node_register_nic(node, nic, macaddr): """ Register existence of a with the given on the given """ - url = object_url('node', node, 'nic', nic) - do_put(url, data={'macaddr': macaddr}) + try: + C.node.add_nic(node, nic, macaddr) + except (NotFoundError, DuplicateError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd def node_delete_nic(node, nic): """Delete a on a """ - url = object_url('node', node, 'nic', nic) - do_delete(url) + try: + C.node.remove_nic(node, nic) + except(NotFoundError, BlockedError) as e: + sys.stderr.write('Error: %s\n' % e.message) + +# url = object_url('node', node, 'nic', nic) +# do_delete(url) @cmd diff --git a/haas/client/client_errors.py b/haas/client/client_errors.py index 28807270..f2074b72 100644 --- a/haas/client/client_errors.py +++ b/haas/client/client_errors.py @@ -6,3 +6,6 @@ class NotFoundError(Exception): class DuplicateError(Exception): pass + +class BlockedError(Exception): + pass diff --git a/haas/client/node.py b/haas/client/node.py index f580c2ca..657c9625 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -23,6 +23,81 @@ def show_node(self, node_name): q = requests.get(url, headers={"Authorization": "Basic %s" %self.auth}) return q.json() + def delete(self, node_name): + """ Deletes the node from database. """ + self.node_name = node_name + url = self.object_url('node', node_name) + q = requests.delete(url, headers={"Authorization": "Basic %s" %self.auth}) + if q.ok: + return + elif q.status_code == 409: + raise BlockedError("Make sure all nics are removed before deleting the node") + elif q.status_code == 404: + raise NotFoundError("No such node exist. Nothing to delete.") + + def power_cycle(self, node_name): + """ Power cycles the """ + self.node_name = node_name + url = self.object_url('node', node_name, 'power_cycle') + q = requests.post(url, headers={"Authorization": "Basic %s" %self.auth}) + if q.ok: + return + elif q.status_code == 409: + raise BlockedError("Operation blocked by other pending operations") + elif q.status_code == 500: + raise NotFoundError("Operation Failed. Contact your system administrator") + + def power_off(self, node_name): + """ Power offs the """ + self.node_name = node_name + url = self.object_url('node', node_name, 'power_off') + q = requests.post(url, headers={"Authorization": "Basic %s" %self.auth}) + if q.ok: + return + elif q.status_code == 409: + raise BlockedError("Operation blocked by other pending operations") + elif q.status_code == 500: + raise NotFoundError("Operation Failed. Contact your system administrator") + + def add_nic(self, node_name, nic_name, macaddr): + """ adds a from """ + self.node_name = node_name + self.nic_name = nic_name + self.macaddr = macaddr + url = self.object_url('node', node_name, 'nic', nic_name) + payload = json.dumps({'macaddr':macaddr}) + q = requests.put(url, headers={"Authorization": "Basic %s" %self.auth}, data=payload) + if q.ok: + return + elif q.status_code == 404: + raise NotFoundError("Nic cannot be added. Node does not exist.") + elif q.status_code == 409: + raise DuplicateError("Nic already exists.") + + + + def remove_nic(self, node_name, nic_name): + """ remove a from """ + self.node_name = node_name + self.nic_name = nic_name + url = self.object_url('node', node_name, 'nic', nic_name) + q = requests.delete(url, headers={"Authorization": "Basic %s" %self.auth}) + if q.ok: + return + elif q.status_code == 404: + raise NotFoundError("Nic not found. Nothing to delete.") + elif q.status_code == 409: + raise BlockedError("Cannot delete nic, diconnect it from network first") + + + def connect_network(self, node_name, nic, network, channel): + """ Connect to on given and """ + pass + + def disconnect_network(self, node_name, nic, network): + """ Disconnect from on the given . """ + pass + @@ -31,3 +106,4 @@ def show_node(self, node_name): + diff --git a/tests/unit/client.py b/tests/unit/client.py index 985b18d2..c5a0cc93 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -37,21 +37,19 @@ from haas.client import client_errors - -MOCK_SWITCH_TYPE = 'http://schema.massopencloud.org/haas/v0/switches/mock' -OBM_TYPE_MOCK = 'http://schema.massopencloud.org/haas/v0/obm/mock' -OBM_TYPE_IPMI = 'http://schema.massopencloud.org/haas/v0/obm/ipmi' - ep = "http://127.0.0.1:8888" or os.environ.get('HAAS_ENDPOINT') username = "hil_user" or os.environ.get('HAAS_USERNAME') password = "hil_pass1234" or os.environ.get('HAAS_PASSWORD') - auth = auth_db(username, password) - C = Client(ep, auth) #Initializing client library +MOCK_SWITCH_TYPE = 'http://schema.massopencloud.org/haas/v0/switches/mock' +OBM_TYPE_MOCK = 'http://schema.massopencloud.org/haas/v0/obm/mock' +OBM_TYPE_IPMI = 'http://schema.massopencloud.org/haas/v0/obm/ipmi' + + ## Following tests check if the client library is initialized correctly def test_auth_db(): From 404a37b416ce76c7a80836a208c6a2b257406186 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 17 Oct 2016 16:49:17 -0400 Subject: [PATCH 22/71] Adds -- Unit tests for testing node related client library calls -- Fixes a test that took unresonably long time to finish. --- haas/cli.py | 1 - tests/unit/client.py | 55 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index d98d0b2c..3b3b9382 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -484,7 +484,6 @@ def node_register(node, subtype, *args): @cmd def node_delete(node): """Delete """ -# import pdb; pdb.set_trace() try: C.node.delete(node) except (NotFoundError, BlockedError) as e: diff --git a/tests/unit/client.py b/tests/unit/client.py index c5a0cc93..fcb4d637 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -119,7 +119,7 @@ def make_config(): 'haas.ext.switches.nexus =', 'haas.ext.switches.dell =', 'haas.ext.switches.brocade =', - + 'haas.ext.obm.mock =', 'haas.ext.obm.ipmi =', 'haas.ext.network_allocators.null =', '[haas.ext.network_allocators.vlan_pool]', @@ -187,6 +187,11 @@ def populate_server(): obminfo6 = {"type": api_nodename+'ipmi', "host":"10.10.0.06", "user":"ipmi_u", "password":"pass1234" } + obminfo7 = {"type": api_nodename+'mock', "host":"10.10.0.07", + "user":"ipmi_u", "password":"pass1234" } + + obminfo8 = {"type": api_nodename+'mock', "host":"10.10.0.08", + "user":"ipmi_u", "password":"pass1234" } requests.put(url_node+'node-01', data=json.dumps({"obm":obminfo1})) requests.put(url_node+'node-02', data=json.dumps({"obm":obminfo2})) @@ -194,6 +199,8 @@ def populate_server(): requests.put(url_node+'node-04', data=json.dumps({"obm":obminfo4})) requests.put(url_node+'node-05', data=json.dumps({"obm":obminfo5})) requests.put(url_node+'node-06', data=json.dumps({"obm":obminfo6})) + requests.put(url_node+'node-07', data=json.dumps({"obm":obminfo7})) + requests.put(url_node+'node-08', data=json.dumps({"obm":obminfo8})) ## Adding Projects proj-01 - proj-03 for i in [ "proj-01", "proj-02", "proj-03" ]: @@ -257,12 +264,50 @@ class Test_Node: def test_list_nodes_free(self): result = C.node.list('free') - assert result == [u'node-06'] + assert result == [u'node-06', u'node-07', u'node-08'] def test_list_nodes_all(self): result = C.node.list('all') assert result == [u'node-01', u'node-02', u'node-03', u'node-04', - u'node-05', u'node-06'] + u'node-05', u'node-06', u'node-07', u'node-08'] + + def test_show_node(self): + result = C.node.show_node('node-07') + assert result == {u'name': u'node-07', u'nics': [], u'project': None} + + def test_power_cycle(self): + result = C.node.power_cycle('node-07') + assert result == None + + def test_power_off(self): + result = C.node.power_off('node-07') + assert result == None + + def test_node_add_nic(self): + result = C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') + assert result == None + + def test_node_add_duplicate_nic(self): + C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') + with pytest.raises(client_errors.DuplicateError): + C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') + + def test_nosuch_node_add_nic(self): + with pytest.raises(client_errors.NotFoundError): + C.node.add_nic('abcd', 'eth0', 'aa:bb:cc:dd:ee:ff') + + def test_remove_nic(self): + C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') + result = C.node.remove_nic('node-07', 'eth0') + assert result == None + + def test_remove_duplicate_nic(self): + C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') + C.node.remove_nic('node-07', 'eth0') + with pytest.raises(client_errors.NotFoundError): + C.node.remove_nic('node-07', 'eth0') + + @pytest.mark.usefixtures("create_setup") class Test_project: @@ -330,7 +375,9 @@ def test_project_connect_node_nosuchobject(self): def test_project_disconnect_node(self): """ Test for correctly disconnecting node from project.""" - result = C.project.disconnect('proj-01', 'node-01') + C.project.create('abcd') + C.project.connect('abcd', 'node-07') + result = C.project.disconnect('abcd', 'node-07') assert result == None # ?? This tests takes same time as sum of all the other tests ?? From 16bb93c343960b5b7d983310352bbdc7a44c570d Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 9 Nov 2016 08:04:18 -0500 Subject: [PATCH 23/71] This commit makes the necessary changes to support keystone as a backend for authentication. Classes in base.py, auth.py and client.py incorporate those changes. Initialization of client library is modified in cli.py Also, all the files are made complaint with pep8 Now client library uses sessions to make http requests. --- haas/cli.py | 26 +-- haas/client/auth.py | 26 ++- haas/client/base.py | 10 +- haas/client/client.py | 16 +- haas/client/{client_errors.py => errors.py} | 0 haas/client/network.py | 12 +- haas/client/node.py | 80 ++++--- haas/client/switch.py | 18 +- tests/unit/client.py | 228 ++++++++++++-------- 9 files changed, 235 insertions(+), 181 deletions(-) rename haas/client/{client_errors.py => errors.py} (100%) diff --git a/haas/cli.py b/haas/cli.py index 65fa90e7..2891a6d8 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -28,9 +28,9 @@ from functools import wraps ## Hook to the client library -from haas.client.auth import * +from haas.client.auth import db_auth from haas.client.client import Client -from haas.client.client_errors import * +from haas.client import errors ep = os.environ.get('HAAS_ENDPOINT') or "http://127.0.0.1:5000" @@ -38,9 +38,9 @@ password = "tumbling" or os.environ.get('HAAS_PASSWORD') -auth = auth_db(username, password) +sess = db_auth(username, password) -C = Client(ep, auth) #Initializing client library +C = Client(ep, sess) #Initializing client library command_dict = {} @@ -408,7 +408,7 @@ def project_create(project): """Create a """ try: C.project.create(project) - except (DuplicateError, AuthenticationError) as e: + except (errors.DuplicateError, errors.AuthenticationError) as e: sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -416,7 +416,7 @@ def project_delete(project): """Delete """ try: C.project.delete(project) - except (NotFoundError, AuthenticationError) as e: + except (errors.NotFoundError, errors.AuthenticationError) as e: sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -438,7 +438,7 @@ def project_connect_node(project, node): """Connect to """ try: C.project.connect(project, node) - except (NotFoundError, DuplicateError) as e: + except (errors.NotFoundError, errors.DuplicateError) as e: sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -446,7 +446,7 @@ def project_detach_node(project, node): """Detach from """ try: C.project.disconnect(project, node) - except (NotFoundError) as e: + except (errors.NotFoundError) as e: sys.stderr.write('Error: %s\n' % e.message) @@ -500,7 +500,7 @@ def node_delete(node): """Delete """ try: C.node.delete(node) - except (NotFoundError, BlockedError) as e: + except (errors.NotFoundError, errors.BlockedError) as e: sys.stderr.write('Error: %s\n' % e.message) # url = object_url('node', node) @@ -512,7 +512,7 @@ def node_power_cycle(node): """Power cycle """ try: C.node.power_cycle(node) - except (NotFoundError, BlockedError) as e: + except (errors.NotFoundError, errors.BlockedError) as e: sys.stderr.write('Error: %s\n' % e.message) # url = object_url('node', node, 'power_cycle') @@ -524,7 +524,7 @@ def node_power_off(node): """Power off """ try: C.node.power_off(node) - except (NotFoundError, BlockedError) as e: + except (errors.NotFoundError, errors.BlockedError) as e: sys.stderr.write('Error: %s\n' % e.message) @@ -535,7 +535,7 @@ def node_register_nic(node, nic, macaddr): """ try: C.node.add_nic(node, nic, macaddr) - except (NotFoundError, DuplicateError) as e: + except (errors.NotFoundError, errors.DuplicateError) as e: sys.stderr.write('Error: %s\n' % e.message) @@ -544,7 +544,7 @@ def node_delete_nic(node, nic): """Delete a on a """ try: C.node.remove_nic(node, nic) - except(NotFoundError, BlockedError) as e: + except(errors.NotFoundError, errors.BlockedError) as e: sys.stderr.write('Error: %s\n' % e.message) # url = object_url('node', node, 'nic', nic) diff --git a/haas/client/auth.py b/haas/client/auth.py index 98a64242..d533b325 100644 --- a/haas/client/auth.py +++ b/haas/client/auth.py @@ -1,13 +1,19 @@ -import base64 - - - -def auth_db(username, password): - concatit = username+":"+password - concatit64 = base64.b64encode(concatit) - - return concatit64 - +import requests + + +def db_auth(username, password): + """ For database as authentication backend, this function prepares + the default http client session using requests module. + """ + s = requests.Session() + s.auth = (username, password) + return s + +def keystone_auth(parameters): + """ This functions setup requisites for the http session when using + keystone as the aunthentication backend. + """ + pass diff --git a/haas/client/base.py b/haas/client/base.py index 6860dba8..61ada514 100644 --- a/haas/client/base.py +++ b/haas/client/base.py @@ -3,10 +3,10 @@ import requests import os import json +from haas.client import errors from urlparse import urljoin -#from requests.exceptions import ConnectionError, MissingSchema -from client_errors import * + class ClientBase(object): @@ -19,7 +19,7 @@ class ClientBase(object): """ - def __init__(self, endpoint=None, auth=None): + def __init__(self, endpoint=None, sess=None): """ Initialize an instance of the library with following parameters. endpoint: stands for the http endpoint eg. endpoint=http://127.0.0.1 user: username as which you wish to connect to HaaS @@ -27,9 +27,9 @@ def __init__(self, endpoint=None, auth=None): Currently all this information is fetched from the user's environment. """ self.endpoint = endpoint - self.auth = auth + self.s = sess - if None in [self.endpoint, self.auth]: + if None in [self.endpoint, self.s]: raise LookupError("Insufficient attributes to establish connection with HaaS server") def object_url(self, *args): diff --git a/haas/client/client.py b/haas/client/client.py index de8a0d50..662adade 100644 --- a/haas/client/client.py +++ b/haas/client/client.py @@ -3,17 +3,15 @@ from haas.client.project import Project from haas.client.switch import Switch from haas.client.network import Network -from haas.client.auth import auth_db - +from haas.client import auth class Client(object): - def __init__(self, endpoint, auth): + def __init__(self, endpoint, sess): self.endpoint = endpoint - self.auth = auth - self.node = Node(self.endpoint, self.auth) - self.project = Project(self.endpoint, self.auth) - self.switch = Switch(self.endpoint, self.auth) - self.network = Network(self.endpoint, self.auth) - + self.s = sess + self.node = Node(self.endpoint, self.s) + self.project = Project(self.endpoint, self.s) + self.switch = Switch(self.endpoint, self.s) + self.network = Network(self.endpoint, self.s) diff --git a/haas/client/client_errors.py b/haas/client/errors.py similarity index 100% rename from haas/client/client_errors.py rename to haas/client/errors.py diff --git a/haas/client/network.py b/haas/client/network.py index 58091099..595f4fb5 100644 --- a/haas/client/network.py +++ b/haas/client/network.py @@ -1,8 +1,7 @@ -import requests import json from haas.client.base import ClientBase -from haas.client.client_errors import * +from haas.client import errors class Network(ClientBase): @@ -12,10 +11,11 @@ class Network(ClientBase): def list(self): """ Lists all projects under HIL """ url = self.object_url('/networks') - q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + q = self.s.get(url) if q.ok: return q.json() elif q.status_code == 401: - raise AuthenticationError("Make sure credentials match chosen authentication backend.") - - + raise errors.AuthenticationError( + "Make sure credentials match " + "chosen authentication backend." + ) diff --git a/haas/client/node.py b/haas/client/node.py index 657c9625..2ddfc9cd 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -1,5 +1,6 @@ -from haas.client.base import * -from haas.client.client_errors import * +import json +from haas.client.base import ClientBase +from haas.client import errors class Node(ClientBase): @@ -9,55 +10,71 @@ class Node(ClientBase): def list(self, is_free): """ List all nodes that HIL manages """ url = self.object_url('nodes', is_free) - q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + q = self.s.get(url) if q.ok: return q.json() elif q.status_code == 401: - raise AuthenticationError("Make sure credentials match chosen authentication backend.") + raise errors.AuthenticationError( + "Make sure credentials match chosen authentication backend." + ) def show_node(self, node_name): """ Shows attributes of a given node """ self.node_name = node_name url = self.object_url('node', node_name) - q = requests.get(url, headers={"Authorization": "Basic %s" %self.auth}) + q = self.s.get(url) return q.json() def delete(self, node_name): """ Deletes the node from database. """ self.node_name = node_name url = self.object_url('node', node_name) - q = requests.delete(url, headers={"Authorization": "Basic %s" %self.auth}) + q = self.s.delete(url) if q.ok: return elif q.status_code == 409: - raise BlockedError("Make sure all nics are removed before deleting the node") + raise errors.BlockedError( + "Make sure all nics are removed before deleting the node" + ) elif q.status_code == 404: - raise NotFoundError("No such node exist. Nothing to delete.") + raise errors.NotFoundError( + "No such node exist. Nothing to delete." + ) def power_cycle(self, node_name): """ Power cycles the """ self.node_name = node_name url = self.object_url('node', node_name, 'power_cycle') - q = requests.post(url, headers={"Authorization": "Basic %s" %self.auth}) + q = self.s.post(url) if q.ok: return elif q.status_code == 409: - raise BlockedError("Operation blocked by other pending operations") + raise errors.BlockedError( + "Operation blocked by other pending operations" + ) elif q.status_code == 500: - raise NotFoundError("Operation Failed. Contact your system administrator") + raise errors.NotFoundError( + "Operation Failed. Contact your system administrator" + ) def power_off(self, node_name): """ Power offs the """ self.node_name = node_name url = self.object_url('node', node_name, 'power_off') - q = requests.post(url, headers={"Authorization": "Basic %s" %self.auth}) + q = self.s.post(url) if q.ok: return + elif q.status_code == 404: + raise errors.NotFoundError("Node not found.") elif q.status_code == 409: - raise BlockedError("Operation blocked by other pending operations") + raise errors.BlockedError( + "Operation blocked by other pending operations" + ) elif q.status_code == 500: - raise NotFoundError("Operation Failed. Contact your system administrator") + raise errors.NotFoundError( + "Operation Failed. Contact your system administrator" + ) def add_nic(self, node_name, nic_name, macaddr): """ adds a from """ @@ -65,30 +82,35 @@ def add_nic(self, node_name, nic_name, macaddr): self.nic_name = nic_name self.macaddr = macaddr url = self.object_url('node', node_name, 'nic', nic_name) - payload = json.dumps({'macaddr':macaddr}) - q = requests.put(url, headers={"Authorization": "Basic %s" %self.auth}, data=payload) + payload = json.dumps({'macaddr': macaddr}) + q = self.s.put(url, data=payload) if q.ok: return elif q.status_code == 404: - raise NotFoundError("Nic cannot be added. Node does not exist.") + raise errors.NotFoundError( + "Nic cannot be added. Node does not exist." + ) elif q.status_code == 409: - raise DuplicateError("Nic already exists.") - - + raise errors.DuplicateError( + "Nic already exists." + ) def remove_nic(self, node_name, nic_name): """ remove a from """ self.node_name = node_name self.nic_name = nic_name url = self.object_url('node', node_name, 'nic', nic_name) - q = requests.delete(url, headers={"Authorization": "Basic %s" %self.auth}) + q = self.s.delete(url) if q.ok: return elif q.status_code == 404: - raise NotFoundError("Nic not found. Nothing to delete.") + raise errors.NotFoundError( + "Nic not found. Nothing to delete." + ) elif q.status_code == 409: - raise BlockedError("Cannot delete nic, diconnect it from network first") - + raise errors.BlockedError( + "Cannot delete nic, diconnect it from network first" + ) def connect_network(self, node_name, nic, network, channel): """ Connect to on given and """ @@ -97,13 +119,3 @@ def connect_network(self, node_name, nic, network, channel): def disconnect_network(self, node_name, nic, network): """ Disconnect from on the given . """ pass - - - - - - - - - - diff --git a/haas/client/switch.py b/haas/client/switch.py index ab981fb1..7b2946f6 100644 --- a/haas/client/switch.py +++ b/haas/client/switch.py @@ -1,5 +1,6 @@ -from haas.client.base import * -from haas.client.client_errors import * +import json +from haas.client.base import ClientBase +from haas.client import errors class Switch(ClientBase): @@ -9,14 +10,11 @@ class Switch(ClientBase): def list(self): """ List all nodes that HIL manages """ url = self.object_url('/switches') - q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + q = self.s.get(url) if q.ok: return q.json() elif q.status_code == 401: - raise AuthenticationError("Make sure credentials match chosen authentication backend.") - - - - - - + raise errors.AuthenticationError( + "Make sure credentials match " + "chosen authentication backend." + ) diff --git a/tests/unit/client.py b/tests/unit/client.py index fcb4d637..0978e5dc 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -29,32 +29,29 @@ from urlparse import urljoin import requests from requests.exceptions import ConnectionError -#from haas.client_lib.client_lib import hilClientLib -## Hook to the client library +# Hook to the client library from haas.client.base import ClientBase -from haas.client.auth import auth_db +from haas.client.auth import db_auth from haas.client.client import Client -from haas.client import client_errors +from haas.client import errors ep = "http://127.0.0.1:8888" or os.environ.get('HAAS_ENDPOINT') username = "hil_user" or os.environ.get('HAAS_USERNAME') password = "hil_pass1234" or os.environ.get('HAAS_PASSWORD') -auth = auth_db(username, password) -C = Client(ep, auth) #Initializing client library - - +sess = db_auth(username, password) +C = Client(ep, sess) # Initializing client library MOCK_SWITCH_TYPE = 'http://schema.massopencloud.org/haas/v0/switches/mock' OBM_TYPE_MOCK = 'http://schema.massopencloud.org/haas/v0/obm/mock' OBM_TYPE_IPMI = 'http://schema.massopencloud.org/haas/v0/obm/ipmi' -## Following tests check if the client library is initialized correctly +# Following tests check if the client library is initialized correctly -def test_auth_db(): - auth = auth_db(username, password) - assert auth.decode('base64', 'strict') == (":").join([username, password]) +def test_db_auth(): + sess = db_auth(username, password) + assert sess.auth == (username, password) class Test_ClientBase: @@ -68,18 +65,19 @@ def test_init_error(self): except LookupError: assert True - def test_correct_init(self): - x = ClientBase(ep, 'some_base64_string') - assert x.endpoint == "http://127.0.0.1:8888" - assert x.auth == "some_base64_string" +# FIX ME: The test may vary based on which backend session is used. +# def test_correct_init(self): +# x = ClientBase(ep, 'some_base64_string') +# assert x.endpoint == "http://127.0.0.1:8888" +# assert x.auth == "some_base64_string" def test_object_url(self): x = ClientBase(ep, 'some_base64_string') y = x.object_url('abc', '123', 'xy23z') assert y == 'http://127.0.0.1:8888/abc/123/xy23z' -#For testing the client library we need a running HIL server, with dummy -#objects populated. Following classes accomplish that end. +# For testing the client library we need a running HIL server, with dummy +# objects populated. Following classes accomplish that end. # It shall: # 1. Configures haas.cfg # 2. Instantiates a database @@ -88,7 +86,7 @@ def test_object_url(self): # 5. tears down the setup in a clean fashion. -#pytest.fixture(scope="module") +# pytest.fixture(scope="module") def make_config(): """ This function creates haas.cfg with desired options @@ -142,7 +140,6 @@ def cleanup((tmpdir, cwd)): os.rmdir(tmpdir) - def initialize_db(): """ Creates an database as defined in haas.cfg.""" check_call(['haas-admin', 'db', 'create']) @@ -158,89 +155,132 @@ def run_server(cmd): proc = Popen(cmd) return proc + def populate_server(): """ Once the server is started, this function will populate some mock objects to faciliate testing of the client library """ - ## Adding nodes, node-01 - node-06 + # Adding nodes, node-01 - node-06 url_node = 'http://127.0.0.1:8888/node/' - api_nodename='http://schema.massopencloud.org/haas/v0/obm/' - - obminfo1 = {"type": api_nodename+'ipmi', "host":"10.10.0.01", - "user":"ipmi_u", "password":"pass1234" } - - obminfo2 = {"type": api_nodename+'ipmi', "host":"10.10.0.02", - "user":"ipmi_u", "password":"pass1234" } - - obminfo3 = {"type": api_nodename+'ipmi', "host":"10.10.0.03", - "user":"ipmi_u", "password":"pass1234" } - - obminfo4 = {"type": api_nodename+'ipmi', "host":"10.10.0.04", - "user":"ipmi_u", "password":"pass1234" } - - - obminfo5 = {"type": api_nodename+'ipmi', "host":"10.10.0.05", - "user":"ipmi_u", "password":"pass1234" } - - obminfo6 = {"type": api_nodename+'ipmi', "host":"10.10.0.06", - "user":"ipmi_u", "password":"pass1234" } - - obminfo7 = {"type": api_nodename+'mock', "host":"10.10.0.07", - "user":"ipmi_u", "password":"pass1234" } - - obminfo8 = {"type": api_nodename+'mock', "host":"10.10.0.08", - "user":"ipmi_u", "password":"pass1234" } - - requests.put(url_node+'node-01', data=json.dumps({"obm":obminfo1})) - requests.put(url_node+'node-02', data=json.dumps({"obm":obminfo2})) - requests.put(url_node+'node-03', data=json.dumps({"obm":obminfo3})) - requests.put(url_node+'node-04', data=json.dumps({"obm":obminfo4})) - requests.put(url_node+'node-05', data=json.dumps({"obm":obminfo5})) - requests.put(url_node+'node-06', data=json.dumps({"obm":obminfo6})) - requests.put(url_node+'node-07', data=json.dumps({"obm":obminfo7})) - requests.put(url_node+'node-08', data=json.dumps({"obm":obminfo8})) - - ## Adding Projects proj-01 - proj-03 - for i in [ "proj-01", "proj-02", "proj-03" ]: + api_nodename = 'http://schema.massopencloud.org/haas/v0/obm/' + + obminfo1 = { + "type": api_nodename+'ipmi', "host": "10.10.0.01", + "user": "ipmi_u", "password": "pass1234" + } + + obminfo2 = { + "type": api_nodename+'ipmi', "host": "10.10.0.02", + "user": "ipmi_u", "password": "pass1234" + } + + obminfo3 = { + "type": api_nodename+'ipmi', "host": "10.10.0.03", + "user": "ipmi_u", "password": "pass1234" + } + + obminfo4 = { + "type": api_nodename+'ipmi', "host": "10.10.0.04", + "user": "ipmi_u", "password": "pass1234" + } + + obminfo5 = { + "type": api_nodename+'ipmi', "host": "10.10.0.05", + "user": "ipmi_u", "password": "pass1234" + } + + obminfo6 = { + "type": api_nodename+'ipmi', "host": "10.10.0.06", + "user": "ipmi_u", "password": "pass1234" + } + + obminfo7 = { + "type": api_nodename+'mock', "host": "10.10.0.07", + "user": "ipmi_u", "password": "pass1234" + } + + obminfo8 = { + "type": api_nodename+'mock', "host": "10.10.0.08", + "user": "ipmi_u", "password": "pass1234" + } + + requests.put(url_node+'node-01', data=json.dumps({"obm": obminfo1})) + requests.put(url_node+'node-02', data=json.dumps({"obm": obminfo2})) + requests.put(url_node+'node-03', data=json.dumps({"obm": obminfo3})) + requests.put(url_node+'node-04', data=json.dumps({"obm": obminfo4})) + requests.put(url_node+'node-05', data=json.dumps({"obm": obminfo5})) + requests.put(url_node+'node-06', data=json.dumps({"obm": obminfo6})) + requests.put(url_node+'node-07', data=json.dumps({"obm": obminfo7})) + requests.put(url_node+'node-08', data=json.dumps({"obm": obminfo8})) + + # Adding Projects proj-01 - proj-03 + for i in ["proj-01", "proj-02", "proj-03"]: requests.put('http://127.0.0.1:8888/project/'+i) - ## Adding switches one for each driver - url='http://127.0.0.1:8888/switch/' - api_name='http://schema.massopencloud.org/haas/v0/switches/' - - dell_param={ 'type':api_name+'powerconnect55xx', 'hostname':'dell-01', - 'username':'root', 'password':'root1234' } - nexus_param={ 'type':api_name+'nexus', 'hostname':'nexus-01', - 'username':'root', 'password':'root1234', 'dummy_vlan':'333' } - mock_param={ 'type':api_name+'mock', 'hostname':'mockSwitch-01', - 'username':'root', 'password':'root1234' } - brocade_param={ 'type':api_name+'brocade', 'hostname':'brocade-01', - 'username':'root', 'password':'root1234', 'interface_type': - 'TenGigabitEthernet' } + # Adding switches one for each driver + url = 'http://127.0.0.1:8888/switch/' + api_name = 'http://schema.massopencloud.org/haas/v0/switches/' + + dell_param = { + 'type': api_name+'powerconnect55xx', 'hostname': 'dell-01', + 'username': 'root', 'password': 'root1234' + } + nexus_param = { + 'type': api_name+'nexus', 'hostname': 'nexus-01', + 'username': 'root', 'password': 'root1234', 'dummy_vlan': '333' + } + mock_param = { + 'type': api_name+'mock', 'hostname': 'mockSwitch-01', + 'username': 'root', 'password': 'root1234' + } + brocade_param = { + 'type': api_name+'brocade', 'hostname': 'brocade-01', + 'username': 'root', 'password': 'root1234', + 'interface_type': 'TenGigabitEthernet' + } requests.put(url+'dell-01', data=json.dumps(dell_param)) requests.put(url+'nexus-01', data=json.dumps(nexus_param)) requests.put(url+'mock-01', data=json.dumps(mock_param)) requests.put(url+'brocade-01', data=json.dumps(brocade_param)) - ## Allocating nodes to projects + # Allocating nodes to projects url_project = 'http://127.0.0.1:8888/project/' # Adding nodes 1 to proj-01 - requests.post( url_project+'proj-01'+'/connect_node', data=json.dumps({'node':'node-01'})) + requests.post( + url_project+'proj-01'+'/connect_node', + data=json.dumps({'node': 'node-01'}) + ) # Adding nodes 2, 4 to proj-02 - requests.post( url_project+'proj-02'+'/connect_node', data=json.dumps({'node':'node-02'})) - requests.post( url_project+'proj-02'+'/connect_node', data=json.dumps({'node':'node-04'})) + requests.post( + url_project+'proj-02'+'/connect_node', + data=json.dumps({'node': 'node-02'}) + ) + requests.post( + url_project+'proj-02'+'/connect_node', + data = json.dumps({'node': 'node-04'}) + ) # Adding node 3, 5 to proj-03 - requests.post( url_project+'proj-03'+'/connect_node', data=json.dumps({'node':'node-03'})) - requests.post( url_project+'proj-03'+'/connect_node', data=json.dumps({'node':'node-05'})) - - ## Assigning networks to projects - url_network='http://127.0.0.1:8888/network/' - for i in [ 'net-01', 'net-02', 'net-03' ]: - requests.put(url_network+i, data=json.dumps({"creator":"proj-01", - "access": "proj-01", "net_id": ""})) + requests.post( + url_project+'proj-03'+'/connect_node', + data = json.dumps({'node': 'node-03'}) + ) + requests.post( + url_project+'proj-03'+'/connect_node', + data = json.dumps({'node': 'node-05'}) + ) + + # Assigning networks to projects + url_network= 'http://127.0.0.1:8888/network/' + for i in ['net-01', 'net-02', 'net-03']: + requests.put( + url_network+i, + data=json.dumps( + {"owner": "proj-01", "access": "proj-01", "net_id": ""} + ) + ) # -- SETUP -- # @@ -289,11 +329,11 @@ def test_node_add_nic(self): def test_node_add_duplicate_nic(self): C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') - with pytest.raises(client_errors.DuplicateError): + with pytest.raises(errors.DuplicateError): C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') def test_nosuch_node_add_nic(self): - with pytest.raises(client_errors.NotFoundError): + with pytest.raises(errors.NotFoundError): C.node.add_nic('abcd', 'eth0', 'aa:bb:cc:dd:ee:ff') def test_remove_nic(self): @@ -304,7 +344,7 @@ def test_remove_nic(self): def test_remove_duplicate_nic(self): C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') C.node.remove_nic('node-07', 'eth0') - with pytest.raises(client_errors.NotFoundError): + with pytest.raises(errors.NotFoundError): C.node.remove_nic('node-07', 'eth0') @@ -338,7 +378,7 @@ def test_project_create(self): def test_duplicate_project_create(self): """ test for catching duplicate name while creating new project. """ C.project.create('dummy-01') - with pytest.raises(client_errors.DuplicateError): + with pytest.raises(errors.DuplicateError): C.project.create('dummy-01') def test_project_delete(self): @@ -349,7 +389,7 @@ def test_project_delete(self): def test_error_project_delete(self): """ test to capture error condition in project delete. """ - with pytest.raises(client_errors.NotFoundError): + with pytest.raises(errors.NotFoundError): C.project.delete('dummy-03') def test_project_connect_node(self): @@ -362,15 +402,15 @@ def test_project_connect_node_duplicate(self): """ test for erronous reconnecting node to project. """ C.project.create('abcd') C.project.connect('abcd', 'node-06') - with pytest.raises(client_errors.DuplicateError): + with pytest.raises(errors.DuplicateError): C.project.connect('abcd', 'node-06') def test_project_connect_node_nosuchobject(self): """ test for connecting no such node or project """ C.project.create('abcd') - with pytest.raises(client_errors.NotFoundError): + with pytest.raises(errors.NotFoundError): C.project.connect('abcd', 'no-such-node') - with pytest.raises(client_errors.NotFoundError): + with pytest.raises(errors.NotFoundError): C.project.connect('no-such-project', 'node-06') def test_project_disconnect_node(self): @@ -384,9 +424,9 @@ def test_project_disconnect_node(self): def test_project_disconnect_node_nosuchobject(self): """ Test for errors while disconnecting node from project.""" C.project.create('abcd') - with pytest.raises(client_errors.NotFoundError): + with pytest.raises(errors.NotFoundError): C.project.disconnect('abcd', 'no-such-node') - with pytest.raises(client_errors.NotFoundError): + with pytest.raises(errors.NotFoundError): C.project.disconnect('no-such-project', 'node-06') @pytest.mark.usefixtures("create_setup") From da671fc52699b8669bc469e85070f810e6cfb92e Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 9 Nov 2016 11:42:45 -0500 Subject: [PATCH 24/71] Removes old client Library --- haas/client/project.py | 70 +++++++++++++++------------ haas/client_lib/__init__.py | 0 haas/client_lib/client_lib.py | 91 ----------------------------------- 3 files changed, 38 insertions(+), 123 deletions(-) delete mode 100644 haas/client_lib/__init__.py delete mode 100644 haas/client_lib/client_lib.py diff --git a/haas/client/project.py b/haas/client/project.py index f35d4d1f..35121ab4 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -1,5 +1,6 @@ -from haas.client.base import * -from haas.client.client_errors import * +import json +from haas.client.base import ClientBase +from haas.client import errors class Project(ClientBase): @@ -9,60 +10,68 @@ class Project(ClientBase): def list(self): """ Lists all projects under HIL """ url = self.object_url('/projects') - q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + q = self.s.get(url) if q.ok: return q.json() elif q.status_code == 401: - raise AuthenticationError("Make sure credentials match chosen authentication backend.") + raise errors.AuthenticationError( + "Make sure credentials match " + "chosen authentication backend." + ) def nodes_in(self, project_name): """ Lists nodes allocated to project """ self.project_name = project_name url = self.object_url('project', self.project_name, 'nodes') - q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + q = self.s.get(url) if q.ok: return q.json() elif q.status_code == 401: - raise AuthenticationError("Invalid credentials.") + raise errors.AuthenticationError("Invalid credentials.") elif q.status_code == 404: - raise NotFoundError("Project %s does not exist." % self.project_name) - + raise errors.NotFoundError( + "Project %s does not exist." % self.project_name + ) def networks_in(self, project_name): """ Lists nodes allocated to project """ self.project_name = project_name url = self.object_url('project', self.project_name, 'networks') - q = requests.get(url, headers={"Authorization": "Basic %s" % self.auth}) + q = self.s.get(url) if q.ok: return q.json() elif q.status_code == 401: - raise AuthenticationError("Invalid credentials.") + raise errors.AuthenticationError("Invalid credentials.") elif q.status_code == 404: - raise NotFoundError("Project %s does not exist." % self.project_name) + raise errors.NotFoundError( + "Project %s does not exist." % self.project_name + ) def create(self, project_name): """ Creates a project named """ self.project_name = project_name url = self.object_url('project', self.project_name) - q = requests.put(url, headers={"Authorization": "Basic %s" % self.auth}) + q = self.s.put(url) if q.ok: return elif q.status_code == 401: - raise AuthenticationError("Invalid credentials.") + raise errors.AuthenticationError("Invalid credentials.") elif q.status_code == 409: - raise DuplicateError("Project Name not unique.") + raise errors.DuplicateError("Project Name not unique.") def delete(self, project_name): """ Deletes a project named """ self.project_name = project_name url = self.object_url('project', self.project_name) - q = requests.delete(url, headers={"Authorization": "Basic %s" % self.auth}) + q = self.s.delete(url) if q.ok: return elif q.status_code == 401: - raise AuthenticationError("Invalid credentials.") + raise errors.AuthenticationError("Invalid credentials.") elif q.status_code == 404: - raise NotFoundError("Deletion failed. Project does not exist.") + raise errors.NotFoundError( + "Deletion failed. Project does not exist." + ) def connect(self, project_name, node_name): """ Adds a node to a project. """ @@ -70,14 +79,16 @@ def connect(self, project_name, node_name): self.node_name = node_name url = self.object_url('project', project_name, 'connect_node') - payload=json.dumps({'node':node_name}) - q = requests.post(url, headers={"Authorization": "Basic %s" % self.auth}, data=payload) + payload = json.dumps({'node': node_name}) + q = self.s.post(url, data=payload) if q.ok: return elif q.status_code == 409: - raise DuplicateError("Node is already owned by the project. ") + raise errors.DuplicateError( + "Node is already owned by the project. " + ) elif q.status_code == 404: - raise NotFoundError("Node or project does not exist. ") + raise errors.NotFoundError("Node or project does not exist.") def disconnect(self, project_name, node_name): """ Adds a node to a project. """ @@ -85,17 +96,12 @@ def disconnect(self, project_name, node_name): self.node_name = node_name url = self.object_url('project', project_name, 'detach_node') - payload=json.dumps({'node':node_name}) - q = requests.post(url, headers={"Authorization": "Basic %s" % self.auth}, data=payload) + payload = json.dumps({'node': node_name}) + q = self.s.post(url, data=payload) if q.ok: return elif q.status_code == 404: - raise NotFoundError("Node or Project does not exist or Node not owned by project") - - - - - - - - + raise errors.NotFoundError( + "Node or Project does not exist or " + "Node not owned by project" + ) diff --git a/haas/client_lib/__init__.py b/haas/client_lib/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/haas/client_lib/client_lib.py b/haas/client_lib/client_lib.py deleted file mode 100644 index a39ac1e3..00000000 --- a/haas/client_lib/client_lib.py +++ /dev/null @@ -1,91 +0,0 @@ -""" This module implements the HaaS client library. """ - -import requests -import os -import json -from urlparse import urljoin -from requests.exceptions import ConnectionError - -from client_errors import * - - -class hilClientLib: - """ Main class which contains all the methods to - -- ensure input complies to API requisites - -- generates correct format for server API on behalf of the client - -- parses output from received from the server. - In case of errors recieved from server, it will generate appropriate - appropriate message. - """ - - - def __init__(self, endpoint=None, user=None, password=None): - """ Initialize an instance of the library with following parameters. - endpoint: stands for the http endpoint eg. endpoint=http://127.0.0.1 - user: username as which you wish to connect to HaaS - password: password for the 'user' as decribed above. - Currently all this information is fetched from the user's environment. - """ - self.endpoint = endpoint or os.environ.get('HAAS_ENDPOINT') - self.user = user or os.environ.get('HAAS_USERNAME') - self.password = password or os.environ.get('HAAS_PASSWORD') - - if None in [self.endpoint, self.user, self.password]: - raise LookupError("Insufficient attributes to establish connection with HaaS server") - - def object_url(self, *args): - """Generate URL from combining endpoint and args as relative URL""" - rel = "/".join(args) - url = urljoin(self.endpoint, rel) - return url - - -## ** QUERY COMMANDS ** - def list_free_nodes(self): - """ Reports available nodes in the free_pool.""" - - url = self.object_url('/free_nodes') - try: - query = requests.get(url) - except ConnectionError as err: - print ("ERROR: Either HIL server is not running or some network issue is stopping me from reaching the server ", err) - else: - return query.json - - - - def user_create(self, username, password, role): - """Create a user with password . - It must be assigned a role either 'admin' or 'regular' - """ - #POINT TO DISCUSS: - #Should role default to 'regular' so that users will - #use the extra flag only in case of making other 'admin' users. - - url = object_url('/auth/basic/user', username) - if role not in ('admin', 'regular'): - raise TypeError("role must be either 'admin' or 'regular'") - - ## This is incomplete. Interfacing with corresponding API call needs some thinking. - - def node_register(node, subtype, *args): - """Register a node named , with the given type - eg. If obm if of type: ipmi then provide arguments relevant to the driver - "ipmi", , , - """ - #FIXME: This needs to have a corresponding API provided by the drivers themselves - #Also, there has to be a way to expose activated drivers to Users. - #Library should be able to provide correct handles generic hooks for all of the drivers. - #My General feeling is that api library should know minimal about the server. - #Currently it violates the Model-View-Controller paradigm, since it has to know - #Which drivers are available and active on the server and what does the third party driver - #api name looks like. Library should not know all this. - - obm_api = "http://schema.massopencloud.org/haas/v0/obm/" - - - - - - - From 2e164cdf4d2788bec74bc89222ef50e3036acbf1 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 9 Nov 2016 12:57:02 -0500 Subject: [PATCH 25/71] Fixes some broken tests. Makes tests/unit/client.py pep8 complaint. --- haas/cli.py | 2 +- haas/client/auth.py | 6 ++---- haas/client/base.py | 9 +++------ tests/unit/client.py | 43 ++++++++++++++++++++++--------------------- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 2891a6d8..1a614773 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -533,7 +533,7 @@ def node_register_nic(node, nic, macaddr): """ Register existence of a with the given on the given """ - try: + try: C.node.add_nic(node, nic, macaddr) except (errors.NotFoundError, errors.DuplicateError) as e: sys.stderr.write('Error: %s\n' % e.message) diff --git a/haas/client/auth.py b/haas/client/auth.py index d533b325..17b715db 100644 --- a/haas/client/auth.py +++ b/haas/client/auth.py @@ -9,11 +9,9 @@ def db_auth(username, password): s.auth = (username, password) return s + def keystone_auth(parameters): - """ This functions setup requisites for the http session when using + """ This functions setup requisites for the http session when using keystone as the aunthentication backend. """ pass - - - diff --git a/haas/client/base.py b/haas/client/base.py index 61ada514..7fab38e4 100644 --- a/haas/client/base.py +++ b/haas/client/base.py @@ -7,8 +7,6 @@ from urlparse import urljoin - - class ClientBase(object): """ Main class which contains all the methods to -- ensure input complies to API requisites @@ -18,7 +16,6 @@ class ClientBase(object): appropriate message. """ - def __init__(self, endpoint=None, sess=None): """ Initialize an instance of the library with following parameters. endpoint: stands for the http endpoint eg. endpoint=http://127.0.0.1 @@ -30,12 +27,12 @@ def __init__(self, endpoint=None, sess=None): self.s = sess if None in [self.endpoint, self.s]: - raise LookupError("Insufficient attributes to establish connection with HaaS server") + raise LookupError( + "Insufficient attributes to establish " + "connection with HaaS server") def object_url(self, *args): """Generate URL from combining endpoint and args as relative URL""" rel = "/".join(args) url = urljoin(self.endpoint, rel) return url - - diff --git a/tests/unit/client.py b/tests/unit/client.py index 0978e5dc..dd02b274 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -260,20 +260,20 @@ def populate_server(): ) requests.post( url_project+'proj-02'+'/connect_node', - data = json.dumps({'node': 'node-04'}) + data=json.dumps({'node': 'node-04'}) ) # Adding node 3, 5 to proj-03 requests.post( url_project+'proj-03'+'/connect_node', - data = json.dumps({'node': 'node-03'}) + data=json.dumps({'node': 'node-03'}) ) requests.post( url_project+'proj-03'+'/connect_node', - data = json.dumps({'node': 'node-05'}) + data=json.dumps({'node': 'node-05'}) ) # Assigning networks to projects - url_network= 'http://127.0.0.1:8888/network/' + url_network = 'http://127.0.0.1:8888/network/' for i in ['net-01', 'net-02', 'net-03']: requests.put( url_network+i, @@ -283,12 +283,12 @@ def populate_server(): ) - # -- SETUP -- # +# -- SETUP -- @pytest.fixture(scope="session") def create_setup(request): dir_names = make_config() initialize_db() - proc = run_server(['haas', 'serve', '8888' ]) + proc = run_server(['haas', 'serve', '8888']) import time time.sleep(1) populate_server() @@ -298,6 +298,7 @@ def fin(): cleanup(dir_names) request.addfinalizer(fin) + @pytest.mark.usefixtures("create_setup") class Test_Node: """ Tests Node related client calls. """ @@ -308,8 +309,10 @@ def test_list_nodes_free(self): def test_list_nodes_all(self): result = C.node.list('all') - assert result == [u'node-01', u'node-02', u'node-03', u'node-04', - u'node-05', u'node-06', u'node-07', u'node-08'] + assert result == [ + u'node-01', u'node-02', u'node-03', u'node-04', u'node-05', + u'node-06', u'node-07', u'node-08' + ] def test_show_node(self): result = C.node.show_node('node-07') @@ -317,15 +320,15 @@ def test_show_node(self): def test_power_cycle(self): result = C.node.power_cycle('node-07') - assert result == None + assert result is None def test_power_off(self): result = C.node.power_off('node-07') - assert result == None + assert result is None def test_node_add_nic(self): result = C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') - assert result == None + assert result is None def test_node_add_duplicate_nic(self): C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') @@ -339,7 +342,7 @@ def test_nosuch_node_add_nic(self): def test_remove_nic(self): C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') result = C.node.remove_nic('node-07', 'eth0') - assert result == None + assert result is None def test_remove_duplicate_nic(self): C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') @@ -348,7 +351,6 @@ def test_remove_duplicate_nic(self): C.node.remove_nic('node-07', 'eth0') - @pytest.mark.usefixtures("create_setup") class Test_project: """ Tests project related client calls.""" @@ -373,7 +375,7 @@ def test_list_networks_inproject(self): def test_project_create(self): """ test for creating project. """ result = C.project.create('dummy-01') - assert result == None + assert result is None def test_duplicate_project_create(self): """ test for catching duplicate name while creating new project. """ @@ -385,7 +387,7 @@ def test_project_delete(self): """ test for deleting project. """ C.project.create('dummy-02') result = C.project.delete('dummy-02') - assert result == None + assert result is None def test_error_project_delete(self): """ test to capture error condition in project delete. """ @@ -396,7 +398,7 @@ def test_project_connect_node(self): """ test for connecting node to project. """ C.project.create('abcd') result = C.project.connect('abcd', 'node-06') - assert result == None + assert result is None def test_project_connect_node_duplicate(self): """ test for erronous reconnecting node to project. """ @@ -418,17 +420,17 @@ def test_project_disconnect_node(self): C.project.create('abcd') C.project.connect('abcd', 'node-07') result = C.project.disconnect('abcd', 'node-07') - assert result == None - # ?? This tests takes same time as sum of all the other tests ?? + assert result is None def test_project_disconnect_node_nosuchobject(self): - """ Test for errors while disconnecting node from project.""" + """ Test for while disconnecting node from project.""" C.project.create('abcd') with pytest.raises(errors.NotFoundError): C.project.disconnect('abcd', 'no-such-node') with pytest.raises(errors.NotFoundError): C.project.disconnect('no-such-project', 'node-06') + @pytest.mark.usefixtures("create_setup") class Test_switch: """ Tests switch related client calls.""" @@ -437,5 +439,4 @@ def test_list_switches(self): result = C.switch.list() assert result == [u'brocade-01', u'dell-01', u'mock-01', u'nexus-01'] -## End of tests ## - +# End of tests ## From e132ea6419458b22059eae9edd5ab008e239427e Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 9 Nov 2016 13:12:58 -0500 Subject: [PATCH 26/71] 1st pass at Integrating keystone authentication with client library. --- haas/cli.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 1a614773..e2287b8b 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -33,14 +33,14 @@ from haas.client import errors -ep = os.environ.get('HAAS_ENDPOINT') or "http://127.0.0.1:5000" -username = "jil" or os.environ.get('HAAS_USERNAME') -password = "tumbling" or os.environ.get('HAAS_PASSWORD') +#ep = os.environ.get('HAAS_ENDPOINT') or "http://127.0.0.1:5000" +#username = "jil" or os.environ.get('HAAS_USERNAME') +#password = "tumbling" or os.environ.get('HAAS_PASSWORD') -sess = db_auth(username, password) +#sess = db_auth(username, password) -C = Client(ep, sess) #Initializing client library +#C = Client(ep, sess) #Initializing client library command_dict = {} @@ -195,13 +195,16 @@ def setup_http_client(): This may be extended with other backends in the future. """ - global http_client + #global http_client # First try basic auth: + ep = os.environ.get('HAAS_ENDPOINT') or "http://127.0.0.1:5000" basic_username = os.getenv('HAAS_USERNAME') basic_password = os.getenv('HAAS_PASSWORD') if basic_username is not None and basic_password is not None: - http_client = RequestsHTTPClient() - http_client.auth = (basic_username, basic_password) + # http_client = RequestsHTTPClient() + #http_client.auth = (basic_username, basic_password) + sess = db_auth(username, password) + C = Client(ep, sess) return # Next try keystone: try: @@ -222,12 +225,14 @@ def setup_http_client(): user_domain_id=os_user_domain_id, project_domain_id=os_project_domain_id) sess = session.Session(auth=auth) - http_client = KeystoneHTTPClient(sess) +# http_client = KeystoneHTTPClient(sess) + C = Client(ep, sess) return except (ImportError, KeyError): pass # Finally, fall back to no authentication: - http_client = requests.Session() + sess = requests.Session() + C = Client(ep, sess) class FailedAPICallException(Exception): From 485ced3d75edb22491d397ef822120dc5d008bdf Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Sat, 10 Dec 2016 20:05:53 -0500 Subject: [PATCH 27/71] Adds user operations related calls in client library and cli. Also includes moving the authentication logic required for keystone backend into appropriate (auth.py) file under client library. --- haas/cli.py | 69 ++++++++++++++++----------------- haas/client/auth.py | 18 ++++++++- haas/client/client.py | 2 + haas/client/network.py | 1 - haas/client/node.py | 18 +++++---- haas/client/user.py | 88 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 46 deletions(-) create mode 100644 haas/client/user.py diff --git a/haas/cli.py b/haas/cli.py index e2287b8b..5a9282c9 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -28,21 +28,11 @@ from functools import wraps ## Hook to the client library -from haas.client.auth import db_auth +from haas.client.auth import db_auth, keystone_auth from haas.client.client import Client from haas.client import errors -#ep = os.environ.get('HAAS_ENDPOINT') or "http://127.0.0.1:5000" -#username = "jil" or os.environ.get('HAAS_USERNAME') -#password = "tumbling" or os.environ.get('HAAS_PASSWORD') - - -#sess = db_auth(username, password) - -#C = Client(ep, sess) #Initializing client library - - command_dict = {} usage_dict = {} MIN_PORT_NUMBER = 1 @@ -137,6 +127,7 @@ def request(self, method, url, data=None, params=None): # An instance of HTTPClient, which will be used to make the request. http_client = None +C = None class InvalidAPIArgumentsException(Exception): @@ -196,6 +187,7 @@ def setup_http_client(): This may be extended with other backends in the future. """ #global http_client + global C #initiating the client library # First try basic auth: ep = os.environ.get('HAAS_ENDPOINT') or "http://127.0.0.1:5000" basic_username = os.getenv('HAAS_USERNAME') @@ -203,13 +195,11 @@ def setup_http_client(): if basic_username is not None and basic_password is not None: # http_client = RequestsHTTPClient() #http_client.auth = (basic_username, basic_password) - sess = db_auth(username, password) + sess = db_auth(basic_username, basic_password) C = Client(ep, sess) return # Next try keystone: try: - from keystoneauth1.identity import v3 - from keystoneauth1 import session os_auth_url = os.getenv('OS_AUTH_URL') os_password = os.getenv('OS_PASSWORD') os_username = os.getenv('OS_USERNAME') @@ -218,14 +208,10 @@ def setup_http_client(): os_project_domain_id = os.getenv('OS_PROJECT_DOMAIN_ID') or 'default' if None in (os_auth_url, os_username, os_password, os_project_name): raise KeyError("Required openstack environment variable not set.") - auth = v3.Password(auth_url=os_auth_url, - username=os_username, - password=os_password, - project_name=os_project_name, - user_domain_id=os_user_domain_id, - project_domain_id=os_project_domain_id) - sess = session.Session(auth=auth) -# http_client = KeystoneHTTPClient(sess) + sess = keystone_auth( + os_auth_url, os_username, os_password, os_user_domain_id, + os_project_name, os_project_domain_id + ) C = Client(ep, sess) return except (ImportError, KeyError): @@ -249,6 +235,8 @@ def check_status_code(response): sys.stdout.write(response.text + "\n") + +# This should be DELETED. # TODO: This function's name is no longer very accurate. As soon as it is # safe, we should change it to something more generic. def object_url(*args): @@ -286,6 +274,8 @@ def do_get(url, params=None): def do_delete(url): check_status_code(http_client.request('DELETE', url)) +# DELETE UPTIL HERE + @cmd def serve(port): @@ -334,13 +324,10 @@ def user_create(username, password, is_admin): may be either "admin" or "regular", and determines whether the user has administrative priveledges. """ - url = object_url('/auth/basic/user', username) - if is_admin not in ('admin', 'regular'): - raise TypeError("is_admin must be either 'admin' or 'regular'") - do_put(url, data={ - 'password': password, - 'is_admin': is_admin == 'admin', - }) + try: + C.user.create(username, password, is_admin) + except (errors.DuplicateError ) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -371,8 +358,11 @@ def network_delete(network): @cmd def user_delete(username): """Delete the user """ - url = object_url('/auth/basic/user', username) - do_delete(url) + + try: + C.user.delete(username) + except (errors.NotFoundError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -385,14 +375,23 @@ def list_projects(): @cmd def user_add_project(user, project): """Add to """ - url = object_url('/auth/basic/user', user, 'add_project') - do_post(url, data={'project': project}) + try: + C.user.grant_access(user, project) + except (errors.NotFoundError, errors.DuplicateError) as e: + sys.stderr.write('Error: %s\n' % e.message) + +# url = object_url('/auth/basic/user', user, 'add_project') +# do_post(url, data={'project': project}) @cmd def user_remove_project(user, project): """Remove from """ - url = object_url('/auth/basic/user', user, 'remove_project') - do_post(url, data={'project': project}) +# url = object_url('/auth/basic/user', user, 'remove_project') +# do_post(url, data={'project': project}) + try: + C.user.remove_access(user, project) + except (errors.NotFoundError, errors.DuplicateError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd def network_grant_project_access(project, network): diff --git a/haas/client/auth.py b/haas/client/auth.py index 17b715db..0ea9a7b4 100644 --- a/haas/client/auth.py +++ b/haas/client/auth.py @@ -1,4 +1,6 @@ import requests +from keystoneauth1.identity import v3 +from keystoneauth1 import session def db_auth(username, password): @@ -10,8 +12,20 @@ def db_auth(username, password): return s -def keystone_auth(parameters): +def keystone_auth( + os_auth_url, os_username, os_password, os_user_domain_id, + os_project_name, os_project_domain_id + ): """ This functions setup requisites for the http session when using keystone as the aunthentication backend. """ - pass + auth = v3.Password( + auth_url=os_auth_url, username=os_username, password=os_password, + user_domain_id=os_user_domain_id, + project_name=os_project_name, + project_domain_id=os_project_domain_id + ) + + s = session.Session(auth=auth) + + return s diff --git a/haas/client/client.py b/haas/client/client.py index 662adade..9c3e36d8 100644 --- a/haas/client/client.py +++ b/haas/client/client.py @@ -3,6 +3,7 @@ from haas.client.project import Project from haas.client.switch import Switch from haas.client.network import Network +from haas.client.user import User from haas.client import auth @@ -15,3 +16,4 @@ def __init__(self, endpoint, sess): self.project = Project(self.endpoint, self.s) self.switch = Switch(self.endpoint, self.s) self.network = Network(self.endpoint, self.s) + self.user = User(self.endpoint, self.s) diff --git a/haas/client/network.py b/haas/client/network.py index 595f4fb5..e9f44036 100644 --- a/haas/client/network.py +++ b/haas/client/network.py @@ -1,5 +1,4 @@ import json - from haas.client.base import ClientBase from haas.client import errors diff --git a/haas/client/node.py b/haas/client/node.py index 2ddfc9cd..0ffb7d16 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -5,11 +5,13 @@ class Node(ClientBase): """ Consists of calls to query and manipulate node related - objects and relations """ + objects and relations. + """ def list(self, is_free): """ List all nodes that HIL manages """ - url = self.object_url('nodes', is_free) + self.is_free = is_free + url = self.object_url('nodes', self.is_free) q = self.s.get(url) if q.ok: return q.json() @@ -22,14 +24,14 @@ def show_node(self, node_name): """ Shows attributes of a given node """ self.node_name = node_name - url = self.object_url('node', node_name) + url = self.object_url('node', self.node_name) q = self.s.get(url) return q.json() def delete(self, node_name): """ Deletes the node from database. """ self.node_name = node_name - url = self.object_url('node', node_name) + url = self.object_url('node', self.node_name) q = self.s.delete(url) if q.ok: return @@ -61,7 +63,7 @@ def power_cycle(self, node_name): def power_off(self, node_name): """ Power offs the """ self.node_name = node_name - url = self.object_url('node', node_name, 'power_off') + url = self.object_url('node', self.node_name, 'power_off') q = self.s.post(url) if q.ok: return @@ -81,8 +83,8 @@ def add_nic(self, node_name, nic_name, macaddr): self.node_name = node_name self.nic_name = nic_name self.macaddr = macaddr - url = self.object_url('node', node_name, 'nic', nic_name) - payload = json.dumps({'macaddr': macaddr}) + url = self.object_url('node', self.node_name, 'nic', self.nic_name) + payload = json.dumps({'macaddr': self.macaddr}) q = self.s.put(url, data=payload) if q.ok: return @@ -99,7 +101,7 @@ def remove_nic(self, node_name, nic_name): """ remove a from """ self.node_name = node_name self.nic_name = nic_name - url = self.object_url('node', node_name, 'nic', nic_name) + url = self.object_url('node', self.node_name, 'nic', self.nic_name) q = self.s.delete(url) if q.ok: return diff --git a/haas/client/user.py b/haas/client/user.py new file mode 100644 index 00000000..cbd73ff9 --- /dev/null +++ b/haas/client/user.py @@ -0,0 +1,88 @@ +import json +from haas.client.base import ClientBase +from haas.client import errors + + +class User(ClientBase): + """ Consists of calls to query and manipulate users related + objects and relations. + """ + + def create(self, username, password, privilege): + """Create a user with password . + may by either "admin" or "regular", and determines whether a + user is authorized for administrative privileges + """ + self.username = username + self.password = password + self.privilege = privilege + url = self.object_url('/auth/basic/user', self.username) + + if privilege not in('admin', 'regular'): + raise TypeError( + "invalid privilege type: must be either 'admin' or 'regular'." + ) + payload = json.dumps({ + 'password': self.password, 'is_admin': privilege == 'admin', + }) + q = self.s.put(url, data=payload) + if q.ok: + return + elif q.status_code == 409: + raise errors.DuplicateError( "User already exists. ") + + def delete(self, username): + """Deletes the user . """ + self.username = username + url = self.object_url('/auth/basic/user', self.username) + q = self.s.delete(url) + if q.ok: + return + elif q.status_code == 404: + raise errors.NotFoundError( + "User deletion failed. No such user. " + ) + + + def grant_access(self, user, project): + """Grants permission to to access resources of . """ + #Note: Named such so that in future we can have granular access to projects + #like "grant_read_access", "grant_write_access", "grant_all_access", etc + self.user = user + self.project = project + url = self.object_url('/auth/basic/user', self.user, 'add_project') + payload = json.dumps({ 'project': self.project }) + q = self.s.post(url, data=payload) + if q.ok: + return + elif q.status_code == 404: + raise errors.NotFoundError( + "Operation failed. Either user or project does not exist. " + ) + elif q.status_code == 409: + raise errors.DuplicateError( + "Access already granted. Duplicate operation. " + ) + + + + def remove_access(self, user, project): + """ Removes all access of to . """ + self.user = user + self.project = project + url = self.object_url('/auth/basic/user', self.user, 'remove_project') + payload = json.dumps({ 'project': self.project }) + q = self.s.post(url, data=payload) + if q.ok: + return + elif q.status_code == 404: + raise errors.NotFoundError( + "Operation failed. Either user; project or their " + "relationship does not exist. " + ) + + + + + + From b9ffe29d0cc814e28f3b1b944af574e65be6101f Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 12 Dec 2016 02:35:52 -0500 Subject: [PATCH 28/71] Adds client library calls for network, node and cli support for them. --- haas/cli.py | 66 +++++++++++++++++++++------------- haas/client/network.py | 82 ++++++++++++++++++++++++++++++++++++++++++ haas/client/node.py | 69 ++++++++++++++++++++++++++++++++--- haas/client/user.py | 4 +++ tests/unit/client.py | 21 +++++++++-- 5 files changed, 211 insertions(+), 31 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 5a9282c9..2f1c42a2 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -333,26 +333,31 @@ def user_create(username, password, is_admin): @cmd def network_create(network, owner, access, net_id): """Create a link-layer . See docs/networks.md for details""" - url = object_url('network', network) - do_put(url, data={'owner': owner, - 'access': access, - 'net_id': net_id}) + + try: + C.network.create(network, owner, access, net_id) + except (errors.DuplicateError, errors.NotFoundError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd def network_create_simple(network, project): """Create owned by project. Specific case of network_create""" - url = object_url('network', network) - do_put(url, data={'owner': project, - 'access': project, - 'net_id': ""}) + try: + C.network.create(network, project, project, "") + except (errors.DuplicateError, errors.NotFoundError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd def network_delete(network): """Delete a """ - url = object_url('network', network) - do_delete(url) +# url = object_url('network', network) +# do_delete(url) + try: + C.network.delete(network) + except (errors.NotFoundError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -396,15 +401,24 @@ def user_remove_project(user, project): @cmd def network_grant_project_access(project, network): """Add to access""" - url = object_url('network', network, 'access', project) - do_put(url) +# url = object_url('network', network, 'access', project) +# do_put(url) + try: + C.network.grant_access(project, network) + except (errors.NotFoundError, errors.DuplicateError) as e: + sys.stderr.write('Error: %s\n' % e.message) + @cmd def network_revoke_project_access(project, network): """Remove from access""" - url = object_url('network', network, 'access', project) - do_delete(url) +# url = object_url('network', network, 'access', project) +# do_delete(url) + try: + C.network.revoke_access(project, network) + except (errors.NotFoundError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -551,9 +565,6 @@ def node_delete_nic(node, nic): except(errors.NotFoundError, errors.BlockedError) as e: sys.stderr.write('Error: %s\n' % e.message) -# url = object_url('node', node, 'nic', nic) -# do_delete(url) - @cmd def headnode_create_hnic(headnode, nic): @@ -569,19 +580,28 @@ def headnode_delete_hnic(headnode, nic): do_delete(url) + @cmd def node_connect_network(node, nic, network, channel): """Connect to on given and """ - url = object_url('node', node, 'nic', nic, 'connect_network') - do_post(url, data={'network': network, - 'channel': channel}) +# url = object_url('node', node, 'nic', nic, 'connect_network') +# do_post(url, data={'network': network, +# 'channel': channel}) + try: + C.node.connect_network(node, nic, network, channel) + except(errors.NotFoundError, errors.DuplicateError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd def node_detach_network(node, nic, network): """Detach from the given on the given """ - url = object_url('node', node, 'nic', nic, 'detach_network') - do_post(url, data={'network': network}) +# url = object_url('node', node, 'nic', nic, 'detach_network') +# do_post(url, data={'network': network}) + try: + C.node.detach_network(node, nic, network) + except(errors.NotFoundError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -675,8 +695,6 @@ def list_switches(): """List all switches""" q = C.switch.list() sys.stdout.write('%s switches : ' %len(q) + " ".join(q) + '\n') -# url = object_url('switches') -# do_get(url) @cmd diff --git a/haas/client/network.py b/haas/client/network.py index e9f44036..e9bd3ada 100644 --- a/haas/client/network.py +++ b/haas/client/network.py @@ -18,3 +18,85 @@ def list(self): "Make sure credentials match " "chosen authentication backend." ) + + def create(self, network, owner, access, net_id): + """ Create a link-layer . See docs/networks.md for + details. + """ + + self.network = network + self.owner = owner + self.access = access + self.net_id = net_id + + url = self.object_url('network', self.network) + payload = json.dumps({ + 'owner': self.owner, 'access': self.access, 'net_id': self.net_id + }) + q = self.s.put(url, data=payload) + if q.ok: + return + elif q.status_code == 409: + raise errors.DuplicateError( + "Network name already exists. " + ) + elif q.status_code == 404: + raise errors.NotFoundError( + "Operation failed. Missing Parameters. " + ) + + def delete(self, network): + """ Delete a . """ + + self.network = network + url = self.object_url('network', self.network) + q = self.s.delete(url) + if q.ok: + return + elif q.status_code == 404: + raise errors.NotFoundError( + "Operation failed. No such network. " + ) + + def grant_access(self, project, network): + """ Grants access to . """ + + self.project = project + self.network = network + + url = self.object_url( + 'network', self.network, 'access', self.project + ) + q = self.s.put(url) + if q.ok: + return + elif q.status_code == 404: + raise errors.NotFoundError( + "Operation failed. Resource does not exist. " + ) + elif q.status_code == 409: + raise errors.DuplicateError( + "Access relationship already exists. " + ) + + + def revoke_access(self, project, network): + """ Removes access of from . """ + + + self.project = project + self.network = network + + url = self.object_url( + 'network', self.network, 'access', self.project + ) + q = self.s.delete(url) + if q.ok: + return + elif q.status_code == 404: + raise errors.NotFoundError( + "Operation failed. Resource or relationship " + "does not exist. " + ) + + diff --git a/haas/client/node.py b/haas/client/node.py index 0ffb7d16..e43978d5 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -28,6 +28,25 @@ def show_node(self, node_name): q = self.s.get(url) return q.json() + + def register(self, node, subtype, *args): + """ Register a node with appropriate OBM driver. """ +# Registering a node requires apriori knowledge of the +# available OBM driver and its corresponding arguments. +# We assume that the HIL administrator is aware as to which +# Node requires which OBM, and knows arguments required +# for successful node registration. + + self.node = node + self.subtype = subtype + obm_api = "http://schema.massopencloud.org/haas/v0/obm/" + obm_types = ["ipmi", "mock"] +# FIXME: In future obm_types should be dynamically fetched +# from haas.cfg, need a new api call for querying available +# and currently active drivers for HIL + pass + + def delete(self, node_name): """ Deletes the node from database. """ self.node_name = node_name @@ -114,10 +133,52 @@ def remove_nic(self, node_name, nic_name): "Cannot delete nic, diconnect it from network first" ) - def connect_network(self, node_name, nic, network, channel): + def connect_network(self, node, nic, network, channel): """ Connect to on given and """ - pass - def disconnect_network(self, node_name, nic, network): + self.node = node + self.nic = nic + self.network = network + self.channel = channel + + url = self.object_url( + 'node', self.node, 'nic', self.nic, 'connect_network' + ) + payload = json.dumps({ + 'network': self.network, 'channel': self.channel + }) + q = self.s.post(url, payload) + if q.ok: + return + if q.status_code == 409: + raise errors.DuplicateError( + "Operation Failed. Relationship already exists. " + ) + if q.status_code == 404: + raise errors.NotFoundError( + "Resource or relationship does not exist. " + ) + + + + + def detach_network(self, node, nic, network): """ Disconnect from on the given . """ - pass + + self.node = node + self.nic = nic + self.network = network + + url = self.object_url( + 'node', self.node, 'nic', self.nic, 'detach_network' + ) + payload = json.dumps({ 'network': self.network }) + q = self.s.post(url, payload) + if q.ok: + return + if q.status_code == 404: + raise errors.NotFoundError( + "Resource or relationship does not exist. " + ) + + diff --git a/haas/client/user.py b/haas/client/user.py index cbd73ff9..7f4a3b8f 100644 --- a/haas/client/user.py +++ b/haas/client/user.py @@ -28,6 +28,10 @@ def create(self, username, password, privilege): q = self.s.put(url, data=payload) if q.ok: return + elif q.status_code == 401: + raise errors.AuthenticationError( + "You do not have rights for user creation " + ) elif q.status_code == 409: raise errors.DuplicateError( "User already exists. ") diff --git a/tests/unit/client.py b/tests/unit/client.py index dd02b274..016f87a0 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -109,9 +109,6 @@ def make_config(): '[database]', 'uri = sqlite:///%s/haas.db' % tmpdir, '[extensions]', - 'haas.ext.auth.null =', - 'haas.ext.network_allocators.null =', - '[extensions]', 'haas.ext.switches.mock =', 'haas.ext.auth.null =', 'haas.ext.switches.nexus =', @@ -439,4 +436,22 @@ def test_list_switches(self): result = C.switch.list() assert result == [u'brocade-01', u'dell-01', u'mock-01', u'nexus-01'] +@pytest.mark.usefixtures("create_setup") +class Test_user: + """ Tests user related client calls.""" + + def test_user_create(self): + """ Test user creation. """ + + result1 = C.user.create('bob', 'pass1234', 'admin') + result2 = C.user.create('bob', 'pass1234', 'regular') + assert result1 is None + assert result2 is None + + + + + + + # End of tests ## From ba23ded26027c828fc01ba0e19333dc8b2c34b6e Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 14 Dec 2016 03:09:30 -0500 Subject: [PATCH 29/71] Adds calls node connect network and switch related calls. Fixes unit tests, to run fast and to accomodate new tests. --- haas/cli.py | 27 ++++++++---- haas/client/auth.py | 1 + haas/client/errors.py | 3 ++ haas/client/node.py | 24 +++++++---- haas/client/project.py | 11 ++--- haas/client/switch.py | 43 +++++++++++++++++++ haas/errors.py | 9 +++- tests/unit/client.py | 94 +++++++++++++++++++++++++++++++----------- 8 files changed, 166 insertions(+), 46 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 2f1c42a2..de0cef83 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -456,7 +456,7 @@ def project_connect_node(project, node): """Connect to """ try: C.project.connect(project, node) - except (errors.NotFoundError, errors.DuplicateError) as e: + except (errors.NotFoundError, errors.BlockedError) as e: sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -686,8 +686,13 @@ def switch_register(switch, subtype, *args): @cmd def switch_delete(switch): """Delete a """ - url = object_url('switch', switch) - do_delete(url) +# url = object_url('switch', switch) +# do_delete(url) + try: + C.switch.delete(switch) + except(errors.NotFoundError, BlockedError) as e: + sys.stderr.write('Error: %s\n' % e.message) + @cmd @@ -769,8 +774,15 @@ def list_project_networks(project): @cmd def show_switch(switch): """Display information about """ - url = object_url('switch', switch) - do_get(url) +# url = object_url('switch', switch) +# do_get(url) + try: + q = C.switch.show(switch) + sys.stdout.write('All switches {}\t: {}\n'.format(len(q), " ".join(q))) + sys.stdout.write(q.json()) + except(errors.NotFoundError) as e: + sys.stderr.write('Error: %s\n' % e.message) + @cmd @@ -793,8 +805,9 @@ def show_node(node): FIXME: Recursion should be implemented to the output. """ q = C.node.show_node(node) - for item in q.items(): - sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) + print q +# for item in q.items(): +# sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) @cmd diff --git a/haas/client/auth.py b/haas/client/auth.py index 0ea9a7b4..2dba5f5c 100644 --- a/haas/client/auth.py +++ b/haas/client/auth.py @@ -1,4 +1,5 @@ import requests +import keystoneauth1.identity from keystoneauth1.identity import v3 from keystoneauth1 import session diff --git a/haas/client/errors.py b/haas/client/errors.py index f2074b72..39b90755 100644 --- a/haas/client/errors.py +++ b/haas/client/errors.py @@ -9,3 +9,6 @@ class DuplicateError(Exception): class BlockedError(Exception): pass + +class ProjectMismatchError(Exception): + pass diff --git a/haas/client/node.py b/haas/client/node.py index e43978d5..7d2d3e47 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -150,16 +150,20 @@ def connect_network(self, node, nic, network, channel): q = self.s.post(url, payload) if q.ok: return - if q.status_code == 409: - raise errors.DuplicateError( - "Operation Failed. Relationship already exists. " - ) if q.status_code == 404: raise errors.NotFoundError( "Resource or relationship does not exist. " ) - - + if q.status_code == 409: + raise errors.DuplicateError( "A network is already attached.") + if q.status_code == 412: + raise errors.ProjectMismatchError( + "Project does not have access to either resource. " + ) + if q.status_code == 423: + raise errors.BlockedError( + "Networking operations pending on this nic. " + ) def detach_network(self, node, nic, network): @@ -176,9 +180,13 @@ def detach_network(self, node, nic, network): q = self.s.post(url, payload) if q.ok: return - if q.status_code == 404: + if q.status_code == 400: raise errors.NotFoundError( - "Resource or relationship does not exist. " + "No such network attached to the nic. " + ) + if q.status_code == 423: + raise errors.BlockedError( + "Networking operations pending on this nic. " ) diff --git a/haas/client/project.py b/haas/client/project.py index 35121ab4..c0a30ed3 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -78,14 +78,15 @@ def connect(self, project_name, node_name): self.project_name = project_name self.node_name = node_name - url = self.object_url('project', project_name, 'connect_node') - payload = json.dumps({'node': node_name}) + url = self.object_url('project', self.project_name, 'connect_node') + payload = json.dumps({'node': self.node_name}) q = self.s.post(url, data=payload) if q.ok: return - elif q.status_code == 409: - raise errors.DuplicateError( - "Node is already owned by the project. " + elif q.status_code == 423: + raise errors.BlockedError( + "Not a free node. Only free nodes can be added to a " + "project. " ) elif q.status_code == 404: raise errors.NotFoundError("Node or project does not exist.") diff --git a/haas/client/switch.py b/haas/client/switch.py index 7b2946f6..225502b8 100644 --- a/haas/client/switch.py +++ b/haas/client/switch.py @@ -18,3 +18,46 @@ def list(self): "Make sure credentials match " "chosen authentication backend." ) + + def register(self, switch, subtype, *args): + """ Registers a switch with name and + model , and relevant arguments in <*args> + """ +# It is assumed that the HIL administrator is aware of +# of the switches HIL will control and has read the +# HIL documentation to use appropriate flags to register +# it with HIL. + + pass + + def delete(self, switch): + self.switch = switch + url = self.object_url('switch', self.switch) + q = self.s.delete(url) + if q.ok: + return + elif q.status_code == 404: + raise errors.NotFoundError( + " Operation failed. Resource does not exist." + ) + elif q.status_code == 409: + raise errors.BlockedError( + " Operation failed. Cannot delete switch." + " Delete its ports first." + ) + + def show(self, switch): + """ Shows attributes of . """ + + self.switch = switch + url = self.object_url('switch', self.switch) + q = self.s.get(url) + if q.ok: + return q.json() + elif q.status_code == 404: + raise errors.NotFoundError( + "Operation failed. No such switch." + ) + + + diff --git a/haas/errors.py b/haas/errors.py index d2f7285f..c8770e2a 100644 --- a/haas/errors.py +++ b/haas/errors.py @@ -80,7 +80,9 @@ class ProjectMismatchError(APIError): """An exception indicating that the resources given don't belong to the same project. """ - status_code = 409 # Conflict + status_code = 412 # was 409. 412 is appropriate here. + # For description read, + # http://www.restpatterns.org/HTTP_Status_Codes/412_-_Precondition_Failed class AuthorizationError(APIError): @@ -92,7 +94,10 @@ class BlockedError(APIError): some other change. For example, deletion is blocked until the components are deleted, and possibly until the dirty flag is cleared as well. """ - status_code = 409 # Conflict + status_code = 423 # was 409, + # This seems to be the closest code for this situation + # For description read, + # http://www.restpatterns.org/HTTP_Status_Codes/423_-_Locked class IllegalStateError(APIError): diff --git a/tests/unit/client.py b/tests/unit/client.py index 016f87a0..a0132db0 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -98,9 +98,9 @@ def make_config(): os.chdir(tmpdir) with open('haas.cfg', 'w') as f: config = '\n'.join([ - '[general]' - 'log_level = debug', - + '[general]', + '[devel]', + 'dry_run=True', '[auth]', 'require_authentication = False', @@ -116,7 +116,7 @@ def make_config(): 'haas.ext.switches.brocade =', 'haas.ext.obm.mock =', 'haas.ext.obm.ipmi =', - 'haas.ext.network_allocators.null =', + 'haas.ext.network_allocators.vlan_pool =', '[haas.ext.network_allocators.vlan_pool]', 'vlans = 1001-1040', @@ -212,12 +212,18 @@ def populate_server(): requests.put(url_node+'node-07', data=json.dumps({"obm": obminfo7})) requests.put(url_node+'node-08', data=json.dumps({"obm": obminfo8})) + # Adding nics to nodes + for i in range(1, 8): + requests.put(url_node+'node-0'+`i`+'/nic/eth0', + data=json.dumps({"macaddr":"aa:bb:cc:dd:ee:0"+`i`})) + + # Adding Projects proj-01 - proj-03 for i in ["proj-01", "proj-02", "proj-03"]: requests.put('http://127.0.0.1:8888/project/'+i) # Adding switches one for each driver - url = 'http://127.0.0.1:8888/switch/' + url_switch = 'http://127.0.0.1:8888/switch/' api_name = 'http://schema.massopencloud.org/haas/v0/switches/' dell_param = { @@ -238,10 +244,23 @@ def populate_server(): 'interface_type': 'TenGigabitEthernet' } - requests.put(url+'dell-01', data=json.dumps(dell_param)) - requests.put(url+'nexus-01', data=json.dumps(nexus_param)) - requests.put(url+'mock-01', data=json.dumps(mock_param)) - requests.put(url+'brocade-01', data=json.dumps(brocade_param)) + requests.put(url_switch+'dell-01', data=json.dumps(dell_param)) + requests.put(url_switch+'nexus-01', data=json.dumps(nexus_param)) + requests.put(url_switch+'mock-01', data=json.dumps(mock_param)) + requests.put(url_switch+'brocade-01', data=json.dumps(brocade_param)) + + #Adding ports to the mock switch, Connect nics to ports: + for i in range(1, 8): + requests.put(url_switch+'mock-01/port/gi1/0/'+`i`) + requests.post(url_switch+'mock-01/port/gi1/0/'+`i`+'/connect_nic', + data=json.dumps({'node':'node-0'+`i`, 'nic': 'eth0'})) + +#Adding port gi1/0/8 to switch mock-01 without connecting it to any node. + requests.put(url_switch+'mock-01/port/gi1/0/8') + + # Adding Projects proj-01 - proj-03 + for i in ["proj-01", "proj-02", "proj-03"]: + requests.put('http://127.0.0.1:8888/project/'+i) # Allocating nodes to projects url_project = 'http://127.0.0.1:8888/project/' @@ -279,19 +298,30 @@ def populate_server(): ) ) + for i in ['net-04', 'net-05']: + requests.put( + url_network+i, + data=json.dumps( + {"owner": "proj-02", "access": "proj-02", "net_id": ""} + ) + ) + + # -- SETUP -- -@pytest.fixture(scope="session") +@pytest.fixture(scope="module", autouse=True) def create_setup(request): dir_names = make_config() initialize_db() - proc = run_server(['haas', 'serve', '8888']) + proc1 = run_server(['haas', 'serve', '8888']) + proc2 = run_server(['haas', 'serve_networks']) import time time.sleep(1) populate_server() def fin(): - proc.terminate() + proc1.terminate() + proc2.terminate() cleanup(dir_names) request.addfinalizer(fin) @@ -313,7 +343,10 @@ def test_list_nodes_all(self): def test_show_node(self): result = C.node.show_node('node-07') - assert result == {u'name': u'node-07', u'nics': [], u'project': None} + assert result == { + u'project': None, u'nics': [{u'macaddr': u'aa:bb:cc:dd:ee:07', + u'networks': {}, u'label': u'eth0'}], u'name': u'node-07' + } def test_power_cycle(self): result = C.node.power_cycle('node-07') @@ -324,28 +357,41 @@ def test_power_off(self): assert result is None def test_node_add_nic(self): - result = C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') + result = C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') assert result is None def test_node_add_duplicate_nic(self): - C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') + C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') with pytest.raises(errors.DuplicateError): - C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') + C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') def test_nosuch_node_add_nic(self): with pytest.raises(errors.NotFoundError): C.node.add_nic('abcd', 'eth0', 'aa:bb:cc:dd:ee:ff') def test_remove_nic(self): - C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') - result = C.node.remove_nic('node-07', 'eth0') + C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') + result = C.node.remove_nic('node-08', 'eth0') assert result is None def test_remove_duplicate_nic(self): - C.node.add_nic('node-07', 'eth0', 'aa:bb:cc:dd:ee:ff') - C.node.remove_nic('node-07', 'eth0') + C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') + C.node.remove_nic('node-08', 'eth0') with pytest.raises(errors.NotFoundError): - C.node.remove_nic('node-07', 'eth0') + C.node.remove_nic('node-08', 'eth0') + + def test_node_connect_network(self): + result = C.node.connect_network( + 'node-01', 'eth0', 'net-01', 'vlan/native' + ) + assert result is None + +#FIXME: I spent some time on this test. Looks like the pytest +#framework kills the network server before it can detach network. +# def test_node_detach_network(self): +# C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') +# result = C.node.detach_network('node-04', 'eth0', 'net-04') +# assert result is None @pytest.mark.usefixtures("create_setup") @@ -400,9 +446,9 @@ def test_project_connect_node(self): def test_project_connect_node_duplicate(self): """ test for erronous reconnecting node to project. """ C.project.create('abcd') - C.project.connect('abcd', 'node-06') - with pytest.raises(errors.DuplicateError): - C.project.connect('abcd', 'node-06') + C.project.connect('abcd', 'node-08') + with pytest.raises(errors.BlockedError): + C.project.connect('abcd', 'node-08') def test_project_connect_node_nosuchobject(self): """ test for connecting no such node or project """ From 174cec73c2276155d153255ba710b487d45e1c03 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 14 Dec 2016 03:26:34 -0500 Subject: [PATCH 30/71] minor changes --- haas/cli.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index de0cef83..dd69730f 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -352,8 +352,6 @@ def network_create_simple(network, project): @cmd def network_delete(network): """Delete a """ -# url = object_url('network', network) -# do_delete(url) try: C.network.delete(network) except (errors.NotFoundError) as e: @@ -521,9 +519,6 @@ def node_delete(node): except (errors.NotFoundError, errors.BlockedError) as e: sys.stderr.write('Error: %s\n' % e.message) -# url = object_url('node', node) -# do_delete(url) - @cmd def node_power_cycle(node): @@ -533,9 +528,6 @@ def node_power_cycle(node): except (errors.NotFoundError, errors.BlockedError) as e: sys.stderr.write('Error: %s\n' % e.message) -# url = object_url('node', node, 'power_cycle') -# do_post(url) - @cmd def node_power_off(node): @@ -584,10 +576,6 @@ def headnode_delete_hnic(headnode, nic): @cmd def node_connect_network(node, nic, network, channel): """Connect to on given and """ -# url = object_url('node', node, 'nic', nic, 'connect_network') -# do_post(url, data={'network': network, -# 'channel': channel}) - try: C.node.connect_network(node, nic, network, channel) except(errors.NotFoundError, errors.DuplicateError) as e: @@ -596,8 +584,6 @@ def node_connect_network(node, nic, network, channel): @cmd def node_detach_network(node, nic, network): """Detach from the given on the given """ -# url = object_url('node', node, 'nic', nic, 'detach_network') -# do_post(url, data={'network': network}) try: C.node.detach_network(node, nic, network) except(errors.NotFoundError) as e: @@ -686,8 +672,6 @@ def switch_register(switch, subtype, *args): @cmd def switch_delete(switch): """Delete a """ -# url = object_url('switch', switch) -# do_delete(url) try: C.switch.delete(switch) except(errors.NotFoundError, BlockedError) as e: @@ -805,9 +789,8 @@ def show_node(node): FIXME: Recursion should be implemented to the output. """ q = C.node.show_node(node) - print q -# for item in q.items(): -# sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) + for item in q.items(): + sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) @cmd From 347090c0c6324b185aa1f2ffdfeb49537ded3ed7 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Thu, 22 Dec 2016 11:22:10 -0500 Subject: [PATCH 31/71] Adds client library calls for most of the api calls except node_register, switch_register and list_network_attachments Also excludes all head_node calls. --- haas/cli.py | 108 ++++++++++++++++++++++------------------- haas/client/client.py | 2 + haas/client/network.py | 21 +++++++- haas/client/node.py | 25 +++++++++- haas/client/switch.py | 59 +++++++++++++++++++++- 5 files changed, 160 insertions(+), 55 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index dd69730f..3b7caaad 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -27,7 +27,7 @@ from functools import wraps -## Hook to the client library +# Hook to the client library from haas.client.auth import db_auth, keystone_auth from haas.client.client import Client from haas.client import errors @@ -186,17 +186,20 @@ def setup_http_client(): This may be extended with other backends in the future. """ - #global http_client - global C #initiating the client library + global http_client + global C # initiating the client library # First try basic auth: ep = os.environ.get('HAAS_ENDPOINT') or "http://127.0.0.1:5000" basic_username = os.getenv('HAAS_USERNAME') basic_password = os.getenv('HAAS_PASSWORD') if basic_username is not None and basic_password is not None: - # http_client = RequestsHTTPClient() - #http_client.auth = (basic_username, basic_password) + # For calls using the client library sess = db_auth(basic_username, basic_password) C = Client(ep, sess) + # For calls with no client library support yet. + # Includes all headnode calls; registration of nodes and switches. + http_client = RequestsHTTPClient() + http_client.auth = (basic_username, basic_password) return # Next try keystone: try: @@ -234,11 +237,11 @@ def check_status_code(response): else: sys.stdout.write(response.text + "\n") - - -# This should be DELETED. +# Function object_url should be DELETED. # TODO: This function's name is no longer very accurate. As soon as it is # safe, we should change it to something more generic. + + def object_url(*args): # Prefer an environmental variable for getting the endpoint if available. url = os.environ.get('HAAS_ENDPOINT') @@ -274,7 +277,7 @@ def do_get(url, params=None): def do_delete(url): check_status_code(http_client.request('DELETE', url)) -# DELETE UPTIL HERE +# DELETE UPTIL HERE once all calls have client library support. @cmd @@ -326,14 +329,13 @@ def user_create(username, password, is_admin): """ try: C.user.create(username, password, is_admin) - except (errors.DuplicateError ) as e: + except (errors.DuplicateError) as e: sys.stderr.write('Error: %s\n' % e.message) @cmd def network_create(network, owner, access, net_id): """Create a link-layer . See docs/networks.md for details""" - try: C.network.create(network, owner, access, net_id) except (errors.DuplicateError, errors.NotFoundError) as e: @@ -361,7 +363,6 @@ def network_delete(network): @cmd def user_delete(username): """Delete the user """ - try: C.user.delete(username) except (errors.NotFoundError) as e: @@ -372,7 +373,7 @@ def user_delete(username): def list_projects(): """List all projects""" q = C.project.list() - sys.stdout.write('%s Projects : ' %len(q) + " ".join(q) + '\n') + sys.stdout.write('%s Projects : ' % len(q) + " ".join(q) + '\n') @cmd @@ -383,36 +384,28 @@ def user_add_project(user, project): except (errors.NotFoundError, errors.DuplicateError) as e: sys.stderr.write('Error: %s\n' % e.message) -# url = object_url('/auth/basic/user', user, 'add_project') -# do_post(url, data={'project': project}) @cmd def user_remove_project(user, project): """Remove from """ -# url = object_url('/auth/basic/user', user, 'remove_project') -# do_post(url, data={'project': project}) try: C.user.remove_access(user, project) except (errors.NotFoundError, errors.DuplicateError) as e: sys.stderr.write('Error: %s\n' % e.message) + @cmd def network_grant_project_access(project, network): """Add to access""" -# url = object_url('network', network, 'access', project) -# do_put(url) try: C.network.grant_access(project, network) except (errors.NotFoundError, errors.DuplicateError) as e: sys.stderr.write('Error: %s\n' % e.message) - @cmd def network_revoke_project_access(project, network): """Remove from access""" -# url = object_url('network', network, 'access', project) -# do_delete(url) try: C.network.revoke_access(project, network) except (errors.NotFoundError) as e: @@ -427,6 +420,7 @@ def project_create(project): except (errors.DuplicateError, errors.AuthenticationError) as e: sys.stderr.write('Error: %s\n' % e.message) + @cmd def project_delete(project): """Delete """ @@ -435,6 +429,7 @@ def project_delete(project): except (errors.NotFoundError, errors.AuthenticationError) as e: sys.stderr.write('Error: %s\n' % e.message) + @cmd def headnode_create(headnode, project, base_img): """Create a in a with """ @@ -442,6 +437,7 @@ def headnode_create(headnode, project, base_img): do_put(url, data={'project': project, 'base_img': base_img}) + @cmd def headnode_delete(headnode): """Delete """ @@ -457,6 +453,7 @@ def project_connect_node(project, node): except (errors.NotFoundError, errors.BlockedError) as e: sys.stderr.write('Error: %s\n' % e.message) + @cmd def project_detach_node(project, node): """Detach from """ @@ -572,7 +569,6 @@ def headnode_delete_hnic(headnode, nic): do_delete(url) - @cmd def node_connect_network(node, nic, network, channel): """Connect to on given and """ @@ -581,6 +577,7 @@ def node_connect_network(node, nic, network, channel): except(errors.NotFoundError, errors.DuplicateError) as e: sys.stderr.write('Error: %s\n' % e.message) + @cmd def node_detach_network(node, nic, network): """Detach from the given on the given """ @@ -678,41 +675,50 @@ def switch_delete(switch): sys.stderr.write('Error: %s\n' % e.message) - @cmd def list_switches(): """List all switches""" q = C.switch.list() - sys.stdout.write('%s switches : ' %len(q) + " ".join(q) + '\n') + sys.stdout.write('%s switches : ' % len(q) + " ".join(q) + '\n') @cmd def port_register(switch, port): """Register a with """ - url = object_url('switch', switch, 'port', port) - do_put(url) + try: + C.port.register(switch, port) + except(errors.DuplicateError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd def port_delete(switch, port): """Delete a from a """ - url = object_url('switch', switch, 'port', port) - do_delete(url) + try: + C.port.delete(switch, port) + except(errors.NotFoundError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd def port_connect_nic(switch, port, node, nic): """Connect a on a to a on a """ - url = object_url('switch', switch, 'port', port, 'connect_nic') - do_post(url, data={'node': node, 'nic': nic}) + try: + C.port.connect_nic(switch, port, node, nic) + except(errors.DuplicateError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd def port_detach_nic(switch, port): """Detach a on a from whatever's connected to it""" - url = object_url('switch', switch, 'port', port, 'detach_nic') - do_post(url) +# url = object_url('switch', switch, 'port', port, 'detach_nic') +# do_post(url) + try: + C.port.detach_nic(switch, port) + except(errors.NotFoundError) as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd def list_network_attachments(network, project): @@ -738,49 +744,52 @@ def list_nodes(is_free): if is_free == 'all': sys.stdout.write('All nodes {}\t: {}\n'.format(len(q), " ".join(q))) elif is_free == 'free': - sys.stdout.write('Free nodes {}\t: {}\n'.format(len(q), " ".join(q))) + sys.stdout.write('Free nodes {}\t: {}\n'.format(len(q), " ".join(q))) else: sys.stdout.write('Error: {} is an invalid argument\n'.format(is_free)) + @cmd def list_project_nodes(project): """List all nodes attached to a """ q = C.project.nodes_in(project) - sys.stdout.write('Nodes allocated to %s: ' %project + " ".join(q) + '\n') + sys.stdout.write('Nodes allocated to %s: ' % project + " ".join(q) + '\n') + @cmd def list_project_networks(project): """List all networks attached to a """ q = C.project.networks_in(project) - sys.stdout.write("Networks allocated to {}\t: {}\n".format(project, " ".join(q))) + sys.stdout.write( + "Networks allocated to {}\t: {}\n".format(project, " ".join(q)) + ) @cmd def show_switch(switch): """Display information about """ -# url = object_url('switch', switch) -# do_get(url) try: q = C.switch.show(switch) - sys.stdout.write('All switches {}\t: {}\n'.format(len(q), " ".join(q))) - sys.stdout.write(q.json()) + for item in q.items(): + sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) except(errors.NotFoundError) as e: sys.stderr.write('Error: %s\n' % e.message) - @cmd def list_networks(): """List all networks""" - url = object_url('networks') - do_get(url) + q = C.network.list() + for item in q.items(): + sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) @cmd def show_network(network): """Display information about """ - q = C.project.networks_in(project) - sys.stdout.write("Networks allocated to {}\t: {}\n".format(project, " ".join(q))) + q = C.network.show(network) + for item in q.items(): + sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) @cmd @@ -788,7 +797,7 @@ def show_node(node): """Display information about a FIXME: Recursion should be implemented to the output. """ - q = C.node.show_node(node) + q = C.node.show(node) for item in q.items(): sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) @@ -824,15 +833,12 @@ def show_console(node): @cmd def start_console(node): """Start logging console output from """ - url = object_url('node', node, 'console') - do_put(url) - + q = C.node.start_console(node) @cmd def stop_console(node): """Stop logging console output from and delete the log""" - url = object_url('node', node, 'console') - do_delete(url) + q = C.node.stop_console(node) @cmd diff --git a/haas/client/client.py b/haas/client/client.py index 9c3e36d8..607f5297 100644 --- a/haas/client/client.py +++ b/haas/client/client.py @@ -2,6 +2,7 @@ from haas.client.node import Node from haas.client.project import Project from haas.client.switch import Switch +from haas.client.switch import Port from haas.client.network import Network from haas.client.user import User from haas.client import auth @@ -15,5 +16,6 @@ def __init__(self, endpoint, sess): self.node = Node(self.endpoint, self.s) self.project = Project(self.endpoint, self.s) self.switch = Switch(self.endpoint, self.s) + self.port = Port(self.endpoint, self.s) self.network = Network(self.endpoint, self.s) self.user = User(self.endpoint, self.s) diff --git a/haas/client/network.py b/haas/client/network.py index e9bd3ada..e9611fb5 100644 --- a/haas/client/network.py +++ b/haas/client/network.py @@ -9,7 +9,7 @@ class Network(ClientBase): def list(self): """ Lists all projects under HIL """ - url = self.object_url('/networks') + url = self.object_url('networks') q = self.s.get(url) if q.ok: return q.json() @@ -18,6 +18,23 @@ def list(self): "Make sure credentials match " "chosen authentication backend." ) + def show(self, network): + """ Shows attributes of a network. """ + self.network = network + url = self.object_url('network', self.network) + q = self.s.get(url) + if q.ok: + return q.json() + elif q.status_code == 401: + raise errors.AuthenticationError( + "Make sure credential match." + "chosen authentication backend." + ) + elif q.status_code == 404: + raise errors.NotFoundError( + "Network does not exist." + ) + def create(self, network, owner, access, net_id): """ Create a link-layer . See docs/networks.md for @@ -30,7 +47,7 @@ def create(self, network, owner, access, net_id): self.net_id = net_id url = self.object_url('network', self.network) - payload = json.dumps({ + payload = json.dumps({ 'owner': self.owner, 'access': self.access, 'net_id': self.net_id }) q = self.s.put(url, data=payload) diff --git a/haas/client/node.py b/haas/client/node.py index 7d2d3e47..53960a25 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -20,7 +20,7 @@ def list(self, is_free): "Make sure credentials match chosen authentication backend." ) - def show_node(self, node_name): + def show(self, node_name): """ Shows attributes of a given node """ self.node_name = node_name @@ -189,4 +189,27 @@ def detach_network(self, node, nic, network): "Networking operations pending on this nic. " ) + def show_console(self, node): + """ Display console log for """ + pass + + + def start_console(self, node): + """ Start logging console output from """ + url = object_url('node', node, 'console') + if q.ok: + return + + + def stop_console(self, node): + """ Stop logging console output from and delete the log""" + url = object_url('node', node, 'console') + q = self.s.delete(url) + if q.ok: + return + + + + + diff --git a/haas/client/switch.py b/haas/client/switch.py index 225502b8..b8670cbb 100644 --- a/haas/client/switch.py +++ b/haas/client/switch.py @@ -20,7 +20,7 @@ def list(self): ) def register(self, switch, subtype, *args): - """ Registers a switch with name and + """ Registers a switch with name and model , and relevant arguments in <*args> """ # It is assumed that the HIL administrator is aware of @@ -60,4 +60,61 @@ def show(self, switch): ) +class Port(ClientBase): + """ Port related operations. """ + + def register(self, switch, port): + """Register a with . """ + self.switch = switch + self.port = port + + url = self.object_url('switch', switch, 'port', port) + q = self.s.put(url) + if q.ok: + return + elif q.status_code == 409: + raise errors.DuplicateError( "Port name not unique. ") + + + def delete(self, switch, port): + """ Deletes information of the for """ + self.switch = switch + self.port = port + + url = self.object_url('switch', switch, 'port', port) + q = self.s.delete(url) + if q.ok: + return + elif q.status_code == 404: + raise errors.NotFoundError( "Port name not found. ") + + def connect_nic(self, switch, port, node, nic): + """ Connects of to of . """ + self.switch = switch + self.port = port + self.node = node + self.nic = nic + + url = self.object_url('switch', switch, 'port', port, 'connect_nic') + payload = json.dumps({ 'node': self.node, 'nic': self.nic }) + q = self.s.post(url, payload) + if q.ok: + return + elif q.status_code == 409: + raise errors.DuplicateError( "Port or Nic already connected. ") + + + def detach_nic(self, switch, port): + """" Detaches of . """ + self.switch = switch + self.port = port + url = self.object_url('switch', switch, 'port', port, 'detach_nic') + q = self.s.post(url) + if q.ok: + return + elif q.status_code == 404: + raise errors.NotFoundError( + "Operation Failed. The relationship does not exist. " + ) + From bcbcd122626873113454daa18d5b639a0f9abffe Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Thu, 22 Dec 2016 12:06:15 -0500 Subject: [PATCH 32/71] Fixes console calls in node.py and adds unit tests --- haas/client/node.py | 7 ++++-- tests/unit/client.py | 53 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/haas/client/node.py b/haas/client/node.py index 53960a25..7929cf7f 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -196,14 +196,17 @@ def show_console(self, node): def start_console(self, node): """ Start logging console output from """ - url = object_url('node', node, 'console') + self.node = node + url = self.object_url('node', self.node, 'console') + q = self.s.put(url) if q.ok: return def stop_console(self, node): """ Stop logging console output from and delete the log""" - url = object_url('node', node, 'console') + self.node = node + url = self.object_url('node', self.node, 'console') q = self.s.delete(url) if q.ok: return diff --git a/tests/unit/client.py b/tests/unit/client.py index a0132db0..1a97b8ee 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -342,7 +342,7 @@ def test_list_nodes_all(self): ] def test_show_node(self): - result = C.node.show_node('node-07') + result = C.node.show('node-07') assert result == { u'project': None, u'nics': [{u'macaddr': u'aa:bb:cc:dd:ee:07', u'networks': {}, u'label': u'eth0'}], u'name': u'node-07' @@ -386,6 +386,15 @@ def test_node_connect_network(self): ) assert result is None + def test_node_start_console(self): + result = C.node.start_console('node-01') + assert result is None + + def test_node_stop_console(self): + result = C.node.stop_console('node-01') + assert result is None + + #FIXME: I spent some time on this test. Looks like the pytest #framework kills the network server before it can detach network. # def test_node_detach_network(self): @@ -482,6 +491,48 @@ def test_list_switches(self): result = C.switch.list() assert result == [u'brocade-01', u'dell-01', u'mock-01', u'nexus-01'] + def test_show_switch(self): + result = C.switch.show('dell-01') + assert result == {u'name': u'dell-01', u'ports': []} + + def test_delete_switch(self): + result = C.switch.delete('nexus-01') + assert result == None + + +@pytest.mark.usefixtures("create_setup") +class Test_port: + """ Tests port related client calls.""" + + def test_port_register(self): + result = C.port.register('dell-01', 'gi1/0/5') + assert result == None + + def test_port_dupregister(self): + C.port.register('dell-01', 'gi1/0/6') + with pytest.raises(errors.DuplicateError): + C.port.register('dell-01', 'gi1/0/6') + + + def test_port_delete(self): + C.port.register('dell-01', 'gi1/0/5') + result = C.port.delete('dell-01', 'gi1/0/5') + assert result == None + + + def test_port_deleteerror(self): + C.port.register('dell-01', 'gi1/0/6') + C.port.delete('dell-01', 'gi1/0/6') + with pytest.raises(errors.NotFoundError): + C.port.delete('dell-01', 'gi1/0/6') + + + def test_port_connect_nic(self): + C.port.register('dell-01', 'abcd') + result = C.port.connect_nic('dell-01', 'abcd', 'node-08', 'eth0') + assert result == None + + @pytest.mark.usefixtures("create_setup") class Test_user: """ Tests user related client calls.""" From 37cf4b08a189c3bb63bef00c23ec6e014dac3b66 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Thu, 22 Dec 2016 15:47:59 -0500 Subject: [PATCH 33/71] adds more unit tests --- tests/unit/client.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 1a97b8ee..fa04002f 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -533,22 +533,42 @@ def test_port_connect_nic(self): assert result == None + def test_port_connect_nic_error(self): + C.port.register('dell-01', 'abcd') + C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:08') + C.port.connect_nic('dell-01', 'abcd', 'node-08', 'eth0') + with pytest.raises(errors.DuplicateError): + C.port.connect_nic('dell-01', 'abcd', 'node-08', 'eth0') + + + def test_port_detach_nic(self): + C.port.register('dell-01', 'gi1/0/11') + C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:08') + C.port.connect_nic('dell-01', 'gi1/0/11', 'node-08', 'eth0') + result = C.port.detach_nic('dell-01', 'gi1/0/11') + assert result == None + + + def test_port_detach_nic_error(self): + C.port.register('dell-01', 'gi1/0/11') + C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:08') + C.port.connect_nic('dell-01', 'gi1/0/11', 'node-08', 'eth0') + C.port.detach_nic('dell-01', 'gi1/0/11') + with pytest.raises(errors.NotFoundError): + C.port.detach_nic('dell-01', 'gi1/0/11') + + @pytest.mark.usefixtures("create_setup") class Test_user: """ Tests user related client calls.""" def test_user_create(self): """ Test user creation. """ - - result1 = C.user.create('bob', 'pass1234', 'admin') + result1 = C.user.create('bill', 'pass1234', 'regular') result2 = C.user.create('bob', 'pass1234', 'regular') assert result1 is None assert result2 is None - - - - # End of tests ## From 21f9d13bbb2a9b56207603dced4e7b16fa127f2b Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Thu, 22 Dec 2016 16:40:55 -0500 Subject: [PATCH 34/71] addresses @zenhacks comments --- haas/cli.py | 1 - haas/client/auth.py | 2 +- haas/client/base.py | 8 +++++--- haas/client/network.py | 2 +- haas/client/node.py | 5 +++-- tests/unit/client.py | 1 - 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 3b7caaad..3b5f5475 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -27,7 +27,6 @@ from functools import wraps -# Hook to the client library from haas.client.auth import db_auth, keystone_auth from haas.client.client import Client from haas.client import errors diff --git a/haas/client/auth.py b/haas/client/auth.py index 2dba5f5c..c54a2749 100644 --- a/haas/client/auth.py +++ b/haas/client/auth.py @@ -6,7 +6,7 @@ def db_auth(username, password): """ For database as authentication backend, this function prepares - the default http client session using requests module. + the default client session. """ s = requests.Session() s.auth = (username, password) diff --git a/haas/client/base.py b/haas/client/base.py index 7fab38e4..65c64d3c 100644 --- a/haas/client/base.py +++ b/haas/client/base.py @@ -19,8 +19,9 @@ class ClientBase(object): def __init__(self, endpoint=None, sess=None): """ Initialize an instance of the library with following parameters. endpoint: stands for the http endpoint eg. endpoint=http://127.0.0.1 + sess: depending on the authentication backend (db vs keystone) the + parameters required to make up the session vary. user: username as which you wish to connect to HaaS - password: password for the 'user' as decribed above. Currently all this information is fetched from the user's environment. """ self.endpoint = endpoint @@ -28,8 +29,9 @@ def __init__(self, endpoint=None, sess=None): if None in [self.endpoint, self.s]: raise LookupError( - "Insufficient attributes to establish " - "connection with HaaS server") + "Incomplete client parameters (username, password, etc) " + "supplied to set up connection with HaaS server" + ) def object_url(self, *args): """Generate URL from combining endpoint and args as relative URL""" diff --git a/haas/client/network.py b/haas/client/network.py index e9611fb5..bc703ab2 100644 --- a/haas/client/network.py +++ b/haas/client/network.py @@ -4,7 +4,7 @@ class Network(ClientBase): - """ Consists of calls to query and manipulate project related + """ Consists of calls to query and manipulate network related objects and relations """ def list(self): diff --git a/haas/client/node.py b/haas/client/node.py index 7929cf7f..d1f45d99 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -76,7 +76,8 @@ def power_cycle(self, node_name): ) elif q.status_code == 500: raise errors.NotFoundError( - "Operation Failed. Contact your system administrator" + "Operation Failed. This is a server-side problem. " + "Contact your HIL Administrator. " ) def power_off(self, node_name): @@ -98,7 +99,7 @@ def power_off(self, node_name): ) def add_nic(self, node_name, nic_name, macaddr): - """ adds a from """ + """ adds a to """ self.node_name = node_name self.nic_name = nic_name self.macaddr = macaddr diff --git a/tests/unit/client.py b/tests/unit/client.py index fa04002f..54ae8e2e 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -29,7 +29,6 @@ from urlparse import urljoin import requests from requests.exceptions import ConnectionError -# Hook to the client library from haas.client.base import ClientBase from haas.client.auth import db_auth from haas.client.client import Client From 559bc40828845a7473e434ccdb2570c1915885d1 Mon Sep 17 00:00:00 2001 From: Naved Ansari Date: Fri, 13 Jan 2017 14:31:07 -0500 Subject: [PATCH 35/71] fixed failing test --- tests/integration/cli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/cli.py b/tests/integration/cli.py index 1933e0ae..55e08140 100644 --- a/tests/integration/cli.py +++ b/tests/integration/cli.py @@ -18,11 +18,13 @@ def test_add_list_delete_projects(): projects = output.split(" ") assert PROJECT1 not in projects assert PROJECT2 not in projects + projects.remove('\n') # remove \n from list size = len(projects) subprocess.check_call(['haas', 'project_create', PROJECT1]) output = subprocess.check_output(['haas', 'list_projects']) projects = output.split(" ") + projects = map(str.strip, projects) # strips off \n from project name assert PROJECT1 in projects assert PROJECT2 not in projects assert len(projects) == size + 1 @@ -30,6 +32,7 @@ def test_add_list_delete_projects(): subprocess.check_call(['haas', 'project_delete', PROJECT1]) output = subprocess.check_output(['haas', 'list_projects']) projects = output.split(" ") + projects.remove('\n') # remove \n from list assert PROJECT1 not in projects assert PROJECT2 not in projects assert len(projects) == size From d65ed0f4bcbf6f4dccc952b4ac8d7643e19a95d9 Mon Sep 17 00:00:00 2001 From: Mengyuan Sun Date: Fri, 13 Jan 2017 23:27:13 -0500 Subject: [PATCH 36/71] fixed pep8 errors in all files except test/unit/client.py --- haas/cli.py | 2 ++ haas/client/__init__.py | 3 --- haas/client/base.py | 2 +- haas/client/errors.py | 4 ++++ haas/client/network.py | 9 +++------ haas/client/node.py | 19 ++++--------------- haas/client/switch.py | 16 ++++++---------- haas/client/user.py | 25 +++++++++---------------- haas/errors.py | 6 +++--- 9 files changed, 32 insertions(+), 54 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 3b5f5475..32d91859 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -719,6 +719,7 @@ def port_detach_nic(switch, port): except(errors.NotFoundError) as e: sys.stderr.write('Error: %s\n' % e.message) + @cmd def list_network_attachments(network, project): """List nodes connected to a network @@ -834,6 +835,7 @@ def start_console(node): """Start logging console output from """ q = C.node.start_console(node) + @cmd def stop_console(node): """Stop logging console output from and delete the log""" diff --git a/haas/client/__init__.py b/haas/client/__init__.py index b28b04f6..e69de29b 100644 --- a/haas/client/__init__.py +++ b/haas/client/__init__.py @@ -1,3 +0,0 @@ - - - diff --git a/haas/client/base.py b/haas/client/base.py index 65c64d3c..da2cb359 100644 --- a/haas/client/base.py +++ b/haas/client/base.py @@ -19,7 +19,7 @@ class ClientBase(object): def __init__(self, endpoint=None, sess=None): """ Initialize an instance of the library with following parameters. endpoint: stands for the http endpoint eg. endpoint=http://127.0.0.1 - sess: depending on the authentication backend (db vs keystone) the + sess: depending on the authentication backend (db vs keystone) the parameters required to make up the session vary. user: username as which you wish to connect to HaaS Currently all this information is fetched from the user's environment. diff --git a/haas/client/errors.py b/haas/client/errors.py index 39b90755..021a667a 100644 --- a/haas/client/errors.py +++ b/haas/client/errors.py @@ -1,14 +1,18 @@ class AuthenticationError(Exception): pass + class NotFoundError(Exception): pass + class DuplicateError(Exception): pass + class BlockedError(Exception): pass + class ProjectMismatchError(Exception): pass diff --git a/haas/client/network.py b/haas/client/network.py index bc703ab2..56ea46b8 100644 --- a/haas/client/network.py +++ b/haas/client/network.py @@ -18,6 +18,7 @@ def list(self): "Make sure credentials match " "chosen authentication backend." ) + def show(self, network): """ Shows attributes of a network. """ self.network = network @@ -35,7 +36,6 @@ def show(self, network): "Network does not exist." ) - def create(self, network, owner, access, net_id): """ Create a link-layer . See docs/networks.md for details. @@ -48,7 +48,8 @@ def create(self, network, owner, access, net_id): url = self.object_url('network', self.network) payload = json.dumps({ - 'owner': self.owner, 'access': self.access, 'net_id': self.net_id + 'owner': self.owner, 'access': self.access, + 'net_id': self.net_id }) q = self.s.put(url, data=payload) if q.ok: @@ -96,11 +97,9 @@ def grant_access(self, project, network): "Access relationship already exists. " ) - def revoke_access(self, project, network): """ Removes access of from . """ - self.project = project self.network = network @@ -115,5 +114,3 @@ def revoke_access(self, project, network): "Operation failed. Resource or relationship " "does not exist. " ) - - diff --git a/haas/client/node.py b/haas/client/node.py index d1f45d99..ba3f6e9e 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -28,14 +28,13 @@ def show(self, node_name): q = self.s.get(url) return q.json() - def register(self, node, subtype, *args): """ Register a node with appropriate OBM driver. """ -# Registering a node requires apriori knowledge of the +# Registering a node requires apriori knowledge of the # available OBM driver and its corresponding arguments. # We assume that the HIL administrator is aware as to which # Node requires which OBM, and knows arguments required -# for successful node registration. +# for successful node registration. self.node = node self.subtype = subtype @@ -46,7 +45,6 @@ def register(self, node, subtype, *args): # and currently active drivers for HIL pass - def delete(self, node_name): """ Deletes the node from database. """ self.node_name = node_name @@ -156,7 +154,7 @@ def connect_network(self, node, nic, network, channel): "Resource or relationship does not exist. " ) if q.status_code == 409: - raise errors.DuplicateError( "A network is already attached.") + raise errors.DuplicateError("A network is already attached.") if q.status_code == 412: raise errors.ProjectMismatchError( "Project does not have access to either resource. " @@ -166,7 +164,6 @@ def connect_network(self, node, nic, network, channel): "Networking operations pending on this nic. " ) - def detach_network(self, node, nic, network): """ Disconnect from on the given . """ @@ -177,7 +174,7 @@ def detach_network(self, node, nic, network): url = self.object_url( 'node', self.node, 'nic', self.nic, 'detach_network' ) - payload = json.dumps({ 'network': self.network }) + payload = json.dumps({'network': self.network}) q = self.s.post(url, payload) if q.ok: return @@ -194,7 +191,6 @@ def show_console(self, node): """ Display console log for """ pass - def start_console(self, node): """ Start logging console output from """ self.node = node @@ -203,7 +199,6 @@ def start_console(self, node): if q.ok: return - def stop_console(self, node): """ Stop logging console output from and delete the log""" self.node = node @@ -211,9 +206,3 @@ def stop_console(self, node): q = self.s.delete(url) if q.ok: return - - - - - - diff --git a/haas/client/switch.py b/haas/client/switch.py index b8670cbb..3f91929f 100644 --- a/haas/client/switch.py +++ b/haas/client/switch.py @@ -23,8 +23,8 @@ def register(self, switch, subtype, *args): """ Registers a switch with name and model , and relevant arguments in <*args> """ -# It is assumed that the HIL administrator is aware of -# of the switches HIL will control and has read the +# It is assumed that the HIL administrator is aware of +# of the switches HIL will control and has read the # HIL documentation to use appropriate flags to register # it with HIL. @@ -73,8 +73,7 @@ def register(self, switch, port): if q.ok: return elif q.status_code == 409: - raise errors.DuplicateError( "Port name not unique. ") - + raise errors.DuplicateError("Port name not unique.") def delete(self, switch, port): """ Deletes information of the for """ @@ -86,7 +85,7 @@ def delete(self, switch, port): if q.ok: return elif q.status_code == 404: - raise errors.NotFoundError( "Port name not found. ") + raise errors.NotFoundError("Port name not found.") def connect_nic(self, switch, port, node, nic): """ Connects of to of . """ @@ -96,13 +95,12 @@ def connect_nic(self, switch, port, node, nic): self.nic = nic url = self.object_url('switch', switch, 'port', port, 'connect_nic') - payload = json.dumps({ 'node': self.node, 'nic': self.nic }) + payload = json.dumps({'node': self.node, 'nic': self.nic}) q = self.s.post(url, payload) if q.ok: return elif q.status_code == 409: - raise errors.DuplicateError( "Port or Nic already connected. ") - + raise errors.DuplicateError("Port or Nic already connected.") def detach_nic(self, switch, port): """" Detaches of . """ @@ -116,5 +114,3 @@ def detach_nic(self, switch, port): raise errors.NotFoundError( "Operation Failed. The relationship does not exist. " ) - - diff --git a/haas/client/user.py b/haas/client/user.py index 7f4a3b8f..33898d90 100644 --- a/haas/client/user.py +++ b/haas/client/user.py @@ -29,11 +29,11 @@ def create(self, username, password, privilege): if q.ok: return elif q.status_code == 401: - raise errors.AuthenticationError( - "You do not have rights for user creation " + raise errors.AuthenticationError( + "You do not have rights for user creation" ) elif q.status_code == 409: - raise errors.DuplicateError( "User already exists. ") + raise errors.DuplicateError("User already exists.") def delete(self, username): """Deletes the user . """ @@ -47,15 +47,16 @@ def delete(self, username): "User deletion failed. No such user. " ) - def grant_access(self, user, project): """Grants permission to to access resources of . """ - #Note: Named such so that in future we can have granular access to projects - #like "grant_read_access", "grant_write_access", "grant_all_access", etc + # Note: Named such so that in future we can have granular + # access to projects + # like "grant_read_access", "grant_write_access", + # "grant_all_access", etc self.user = user self.project = project url = self.object_url('/auth/basic/user', self.user, 'add_project') - payload = json.dumps({ 'project': self.project }) + payload = json.dumps({'project': self.project}) q = self.s.post(url, data=payload) if q.ok: return @@ -68,14 +69,12 @@ def grant_access(self, user, project): "Access already granted. Duplicate operation. " ) - - def remove_access(self, user, project): """ Removes all access of to . """ self.user = user self.project = project url = self.object_url('/auth/basic/user', self.user, 'remove_project') - payload = json.dumps({ 'project': self.project }) + payload = json.dumps({'project': self.project}) q = self.s.post(url, data=payload) if q.ok: return @@ -84,9 +83,3 @@ def remove_access(self, user, project): "Operation failed. Either user; project or their " "relationship does not exist. " ) - - - - - - diff --git a/haas/errors.py b/haas/errors.py index c8770e2a..59989e45 100644 --- a/haas/errors.py +++ b/haas/errors.py @@ -94,9 +94,9 @@ class BlockedError(APIError): some other change. For example, deletion is blocked until the components are deleted, and possibly until the dirty flag is cleared as well. """ - status_code = 423 # was 409, - # This seems to be the closest code for this situation - # For description read, + status_code = 423 # was 409, + # This seems to be the closest code for this situation + # For description read, # http://www.restpatterns.org/HTTP_Status_Codes/423_-_Locked From bac7d394f37fbec85fe44470b2c9dd088f0b96aa Mon Sep 17 00:00:00 2001 From: gfaline Date: Tue, 17 Jan 2017 14:15:03 -0500 Subject: [PATCH 37/71] pep8 issues fixed --- tests/unit/client.py | 97 ++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 54ae8e2e..79b70169 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -163,127 +163,127 @@ def populate_server(): api_nodename = 'http://schema.massopencloud.org/haas/v0/obm/' obminfo1 = { - "type": api_nodename+'ipmi', "host": "10.10.0.01", + "type": api_nodename + 'ipmi', "host": "10.10.0.01", "user": "ipmi_u", "password": "pass1234" } obminfo2 = { - "type": api_nodename+'ipmi', "host": "10.10.0.02", + "type": api_nodename + 'ipmi', "host": "10.10.0.02", "user": "ipmi_u", "password": "pass1234" } obminfo3 = { - "type": api_nodename+'ipmi', "host": "10.10.0.03", + "type": api_nodename + 'ipmi', "host": "10.10.0.03", "user": "ipmi_u", "password": "pass1234" } obminfo4 = { - "type": api_nodename+'ipmi', "host": "10.10.0.04", + "type": api_nodename + 'ipmi', "host": "10.10.0.04", "user": "ipmi_u", "password": "pass1234" } obminfo5 = { - "type": api_nodename+'ipmi', "host": "10.10.0.05", + "type": api_nodename + 'ipmi', "host": "10.10.0.05", "user": "ipmi_u", "password": "pass1234" } obminfo6 = { - "type": api_nodename+'ipmi', "host": "10.10.0.06", + "type": api_nodename + 'ipmi', "host": "10.10.0.06", "user": "ipmi_u", "password": "pass1234" } obminfo7 = { - "type": api_nodename+'mock', "host": "10.10.0.07", + "type": api_nodename + 'mock', "host": "10.10.0.07", "user": "ipmi_u", "password": "pass1234" } obminfo8 = { - "type": api_nodename+'mock', "host": "10.10.0.08", + "type": api_nodename + 'mock', "host": "10.10.0.08", "user": "ipmi_u", "password": "pass1234" } - requests.put(url_node+'node-01', data=json.dumps({"obm": obminfo1})) - requests.put(url_node+'node-02', data=json.dumps({"obm": obminfo2})) - requests.put(url_node+'node-03', data=json.dumps({"obm": obminfo3})) - requests.put(url_node+'node-04', data=json.dumps({"obm": obminfo4})) - requests.put(url_node+'node-05', data=json.dumps({"obm": obminfo5})) - requests.put(url_node+'node-06', data=json.dumps({"obm": obminfo6})) - requests.put(url_node+'node-07', data=json.dumps({"obm": obminfo7})) - requests.put(url_node+'node-08', data=json.dumps({"obm": obminfo8})) + requests.put(url_node + 'node-01', data=json.dumps({"obm": obminfo1})) + requests.put(url_node + 'node-02', data=json.dumps({"obm": obminfo2})) + requests.put(url_node + 'node-03', data=json.dumps({"obm": obminfo3})) + requests.put(url_node + 'node-04', data=json.dumps({"obm": obminfo4})) + requests.put(url_node + 'node-05', data=json.dumps({"obm": obminfo5})) + requests.put(url_node + 'node-06', data=json.dumps({"obm": obminfo6})) + requests.put(url_node + 'node-07', data=json.dumps({"obm": obminfo7})) + requests.put(url_node + 'node-08', data=json.dumps({"obm": obminfo8})) # Adding nics to nodes for i in range(1, 8): - requests.put(url_node+'node-0'+`i`+'/nic/eth0', - data=json.dumps({"macaddr":"aa:bb:cc:dd:ee:0"+`i`})) - + requests.put(url_node + 'node-0' + repr(i) + '/nic/eth0', + data=json.dumps({"macaddr": "aa:bb:cc:dd:ee:0" + repr(i)})) # Adding Projects proj-01 - proj-03 for i in ["proj-01", "proj-02", "proj-03"]: - requests.put('http://127.0.0.1:8888/project/'+i) + requests.put('http://127.0.0.1:8888/project/' + i) # Adding switches one for each driver url_switch = 'http://127.0.0.1:8888/switch/' api_name = 'http://schema.massopencloud.org/haas/v0/switches/' dell_param = { - 'type': api_name+'powerconnect55xx', 'hostname': 'dell-01', + 'type': api_name + 'powerconnect55xx', 'hostname': 'dell-01', 'username': 'root', 'password': 'root1234' } nexus_param = { - 'type': api_name+'nexus', 'hostname': 'nexus-01', + 'type': api_name + 'nexus', 'hostname': 'nexus-01', 'username': 'root', 'password': 'root1234', 'dummy_vlan': '333' } mock_param = { - 'type': api_name+'mock', 'hostname': 'mockSwitch-01', + 'type': api_name + 'mock', 'hostname': 'mockSwitch-01', 'username': 'root', 'password': 'root1234' } brocade_param = { - 'type': api_name+'brocade', 'hostname': 'brocade-01', + 'type': api_name + 'brocade', 'hostname': 'brocade-01', 'username': 'root', 'password': 'root1234', 'interface_type': 'TenGigabitEthernet' } - requests.put(url_switch+'dell-01', data=json.dumps(dell_param)) - requests.put(url_switch+'nexus-01', data=json.dumps(nexus_param)) - requests.put(url_switch+'mock-01', data=json.dumps(mock_param)) - requests.put(url_switch+'brocade-01', data=json.dumps(brocade_param)) + requests.put(url_switch + 'dell-01', data=json.dumps(dell_param)) + requests.put(url_switch + 'nexus-01', data=json.dumps(nexus_param)) + requests.put(url_switch + 'mock-01', data=json.dumps(mock_param)) + requests.put(url_switch + 'brocade-01', data=json.dumps(brocade_param)) #Adding ports to the mock switch, Connect nics to ports: for i in range(1, 8): - requests.put(url_switch+'mock-01/port/gi1/0/'+`i`) - requests.post(url_switch+'mock-01/port/gi1/0/'+`i`+'/connect_nic', - data=json.dumps({'node':'node-0'+`i`, 'nic': 'eth0'})) + requests.put(url_switch + 'mock-01/port/gi1/0/' + repr(i)) + requests.post(url_switch + 'mock-01/port/gi1/0/' + repr(i) + + '/connect_nic', + data=json.dumps({'node': 'node-0' + repr(i), 'nic': 'eth0'})) #Adding port gi1/0/8 to switch mock-01 without connecting it to any node. - requests.put(url_switch+'mock-01/port/gi1/0/8') + requests.put(url_switch + 'mock-01/port/gi1/0/8') # Adding Projects proj-01 - proj-03 for i in ["proj-01", "proj-02", "proj-03"]: - requests.put('http://127.0.0.1:8888/project/'+i) + requests.put('http://127.0.0.1:8888/project/' + i) # Allocating nodes to projects url_project = 'http://127.0.0.1:8888/project/' # Adding nodes 1 to proj-01 requests.post( - url_project+'proj-01'+'/connect_node', + url_project + 'proj-01' + '/connect_node', data=json.dumps({'node': 'node-01'}) ) # Adding nodes 2, 4 to proj-02 requests.post( - url_project+'proj-02'+'/connect_node', + url_project + 'proj-02' + '/connect_node', data=json.dumps({'node': 'node-02'}) ) requests.post( - url_project+'proj-02'+'/connect_node', + url_project + 'proj-02' + '/connect_node', data=json.dumps({'node': 'node-04'}) ) # Adding node 3, 5 to proj-03 requests.post( - url_project+'proj-03'+'/connect_node', + url_project + 'proj-03' + '/connect_node', data=json.dumps({'node': 'node-03'}) ) requests.post( - url_project+'proj-03'+'/connect_node', + url_project + 'proj-03' + '/connect_node', data=json.dumps({'node': 'node-05'}) ) @@ -291,7 +291,7 @@ def populate_server(): url_network = 'http://127.0.0.1:8888/network/' for i in ['net-01', 'net-02', 'net-03']: requests.put( - url_network+i, + url_network + i, data=json.dumps( {"owner": "proj-01", "access": "proj-01", "net_id": ""} ) @@ -299,14 +299,13 @@ def populate_server(): for i in ['net-04', 'net-05']: requests.put( - url_network+i, + url_network + i, data=json.dumps( {"owner": "proj-02", "access": "proj-02", "net_id": ""} ) ) - # -- SETUP -- @pytest.fixture(scope="module", autouse=True) def create_setup(request): @@ -395,11 +394,11 @@ def test_node_stop_console(self): #FIXME: I spent some time on this test. Looks like the pytest -#framework kills the network server before it can detach network. -# def test_node_detach_network(self): -# C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') -# result = C.node.detach_network('node-04', 'eth0', 'net-04') -# assert result is None +#framework kills the network server before it can detach network. +#def test_node_detach_network(self): +#C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') +# result = C.node.detach_network('node-04', 'eth0', 'net-04') +# assert result is None @pytest.mark.usefixtures("create_setup") @@ -512,26 +511,22 @@ def test_port_dupregister(self): with pytest.raises(errors.DuplicateError): C.port.register('dell-01', 'gi1/0/6') - def test_port_delete(self): C.port.register('dell-01', 'gi1/0/5') result = C.port.delete('dell-01', 'gi1/0/5') assert result == None - def test_port_deleteerror(self): C.port.register('dell-01', 'gi1/0/6') C.port.delete('dell-01', 'gi1/0/6') with pytest.raises(errors.NotFoundError): C.port.delete('dell-01', 'gi1/0/6') - def test_port_connect_nic(self): C.port.register('dell-01', 'abcd') result = C.port.connect_nic('dell-01', 'abcd', 'node-08', 'eth0') assert result == None - def test_port_connect_nic_error(self): C.port.register('dell-01', 'abcd') C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:08') @@ -539,7 +534,6 @@ def test_port_connect_nic_error(self): with pytest.raises(errors.DuplicateError): C.port.connect_nic('dell-01', 'abcd', 'node-08', 'eth0') - def test_port_detach_nic(self): C.port.register('dell-01', 'gi1/0/11') C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:08') @@ -547,7 +541,6 @@ def test_port_detach_nic(self): result = C.port.detach_nic('dell-01', 'gi1/0/11') assert result == None - def test_port_detach_nic_error(self): C.port.register('dell-01', 'gi1/0/11') C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:08') From 98967cdfd2587ce4f8448b007d7c078c805f7583 Mon Sep 17 00:00:00 2001 From: gfaline Date: Tue, 17 Jan 2017 15:09:44 -0500 Subject: [PATCH 38/71] fixed a couple more pep8 errors --- tests/unit/class_resolver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/class_resolver.py b/tests/unit/class_resolver.py index 1e19897b..1aa3ac13 100644 --- a/tests/unit/class_resolver.py +++ b/tests/unit/class_resolver.py @@ -59,11 +59,11 @@ def test_class_resolver(): def test_class_Obm(): build_class_map_for(Obm) - assert concrete_class_for(Obm, mockapi_name+"obm/mock") \ + assert concrete_class_for(Obm, mockapi_name + "obm/mock") \ is haas.ext.obm.mock.MockObm def test_class_Switch(): build_class_map_for(Switch) - assert concrete_class_for(Switch, mockapi_name+"switches/mock") \ + assert concrete_class_for(Switch, mockapi_name + "switches/mock") \ is haas.ext.switches.mock.MockSwitch From 0556f3d0d0749fe5a0f294fdd4216f8e8c0750f3 Mon Sep 17 00:00:00 2001 From: gfaline Date: Tue, 17 Jan 2017 15:10:33 -0500 Subject: [PATCH 39/71] fixed pep8 issues --- tests/unit/dev_support.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/unit/dev_support.py b/tests/unit/dev_support.py index 6932d2e8..6324cb23 100644 --- a/tests/unit/dev_support.py +++ b/tests/unit/dev_support.py @@ -54,13 +54,17 @@ def _wet(func): # Actual test cases: -def test_dry_function(): _dry(_function) +def test_dry_function(): + _dry(_function) -def test_wet_function(): _wet(_function) +def test_wet_function(): + _wet(_function) -def test_dry_method(): _dry(_method) +def test_dry_method(): + _dry(_method) -def test_wet_method(): _wet(_method) +def test_wet_method(): + _wet(_method) From 7b6523e7413ea79a6a276b5c3a937ae983fe7ebf Mon Sep 17 00:00:00 2001 From: meng-sun Date: Wed, 18 Jan 2017 16:19:42 -0500 Subject: [PATCH 40/71] type error fix that aligns with pep8 fix ('str','str') which had been used as a pep8 fix was throwing a type error. 'str' 'str' allows the pep8 test to go through without throwing a type error. --- tests/unit/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 4a4d184d..5518d989 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -251,8 +251,8 @@ def populate_server(): # Adding ports to the mock switch, Connect nics to ports: for i in range(1, 8): requests.put(url_switch + 'mock-01/port/gi1/0/' + repr(i)) - requests.post(url_switch + 'mock-01/port/gi1/0/' + repr(i) + ('/', - 'connect_nic'), data=json.dumps( + requests.post(url_switch + 'mock-01/port/gi1/0/' + repr(i) + '/' + 'connect_nic', data=json.dumps( {'node': 'node-0' + repr(i), 'nic': 'eth0'} )) From 7fe634dbdc9352c046e69a5830a325b80007c59e Mon Sep 17 00:00:00 2001 From: Mengyuan Sun Date: Wed, 18 Jan 2017 19:41:46 -0500 Subject: [PATCH 41/71] assertionError problems fixed --- tests/unit/client.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 4a4d184d..06c2635f 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -251,8 +251,8 @@ def populate_server(): # Adding ports to the mock switch, Connect nics to ports: for i in range(1, 8): requests.put(url_switch + 'mock-01/port/gi1/0/' + repr(i)) - requests.post(url_switch + 'mock-01/port/gi1/0/' + repr(i) + ('/', - 'connect_nic'), data=json.dumps( + requests.post(url_switch + 'mock-01/port/gi1/0/' + repr(i) + '/' + 'connect_nic', data=json.dumps( {'node': 'node-0' + repr(i), 'nic': 'eth0'} )) @@ -499,8 +499,7 @@ def test_show_switch(self): def test_delete_switch(self): result = C.switch.delete('nexus-01') - if result is None: - raise AssertionError() + assert result is None @pytest.mark.usefixtures("create_setup") @@ -509,8 +508,7 @@ class Test_port: def test_port_register(self): result = C.port.register('dell-01', 'gi1/0/5') - if result is None: - raise AssertionError() + assert result is None def test_port_dupregister(self): C.port.register('dell-01', 'gi1/0/6') @@ -520,8 +518,7 @@ def test_port_dupregister(self): def test_port_delete(self): C.port.register('dell-01', 'gi1/0/5') result = C.port.delete('dell-01', 'gi1/0/5') - if result is None: - raise AssertionError() + assert result is None def test_port_deleteerror(self): C.port.register('dell-01', 'gi1/0/6') @@ -532,8 +529,7 @@ def test_port_deleteerror(self): def test_port_connect_nic(self): C.port.register('dell-01', 'abcd') result = C.port.connect_nic('dell-01', 'abcd', 'node-08', 'eth0') - if result is None: - raise AssertionError() + assert result is None def test_port_connect_nic_error(self): C.port.register('dell-01', 'abcd') @@ -547,8 +543,7 @@ def test_port_detach_nic(self): C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:08') C.port.connect_nic('dell-01', 'gi1/0/11', 'node-08', 'eth0') result = C.port.detach_nic('dell-01', 'gi1/0/11') - if result is None: - raise AssertionError() + assert result is None def test_port_detach_nic_error(self): C.port.register('dell-01', 'gi1/0/11') @@ -567,10 +562,7 @@ def test_user_create(self): """ Test user creation. """ result1 = C.user.create('bill', 'pass1234', 'regular') result2 = C.user.create('bob', 'pass1234', 'regular') - if result1 is None: - raise AssertionError() - - if result2 is None: - raise AssertionError() + assert result1 is None + assert result2 is None # End of tests ## From e22c21081566109d3dd5b6bfd3f3a4034d8a5450 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 31 Jan 2017 22:52:43 -0500 Subject: [PATCH 42/71] Addresses comments from reviewers. Fixes unit tests as a result of those fixes. --- haas/client/node.py | 3 +- haas/client/project.py | 2 +- tests/unit/client.py | 80 +++++++++--------------------------------- 3 files changed, 19 insertions(+), 66 deletions(-) diff --git a/haas/client/node.py b/haas/client/node.py index ba3f6e9e..6b6ffc52 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -93,7 +93,8 @@ def power_off(self, node_name): ) elif q.status_code == 500: raise errors.NotFoundError( - "Operation Failed. Contact your system administrator" + "Operation Failed. This is a server-side problem. " + "Contact your HIL Administrator. " ) def add_nic(self, node_name, nic_name, macaddr): diff --git a/haas/client/project.py b/haas/client/project.py index c0a30ed3..4b8e80ef 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -91,7 +91,7 @@ def connect(self, project_name, node_name): elif q.status_code == 404: raise errors.NotFoundError("Node or project does not exist.") - def disconnect(self, project_name, node_name): + def detach(self, project_name, node_name): """ Adds a node to a project. """ self.project_name = project_name self.node_name = node_name diff --git a/tests/unit/client.py b/tests/unit/client.py index 06c2635f..d56ed2c5 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -16,11 +16,9 @@ import haas from haas import model, api, deferred, server, config from haas.model import db -from haas.test_common import * from haas.network_allocator import get_network_allocator import pytest import json - import requests import os import tempfile @@ -162,57 +160,14 @@ def populate_server(): url_node = 'http://127.0.0.1:8888/node/' api_nodename = 'http://schema.massopencloud.org/haas/v0/obm/' - obminfo1 = { - "type": api_nodename + 'ipmi', "host": "10.10.0.01", - "user": "ipmi_u", "password": "pass1234" - } - - obminfo2 = { - "type": api_nodename + 'ipmi', "host": "10.10.0.02", - "user": "ipmi_u", "password": "pass1234" - } - - obminfo3 = { - "type": api_nodename + 'ipmi', "host": "10.10.0.03", - "user": "ipmi_u", "password": "pass1234" - } - - obminfo4 = { - "type": api_nodename + 'ipmi', "host": "10.10.0.04", - "user": "ipmi_u", "password": "pass1234" - } - - obminfo5 = { - "type": api_nodename + 'ipmi', "host": "10.10.0.05", - "user": "ipmi_u", "password": "pass1234" - } - - obminfo6 = { - "type": api_nodename + 'ipmi', "host": "10.10.0.06", - "user": "ipmi_u", "password": "pass1234" - } - - obminfo7 = { - "type": api_nodename + 'mock', "host": "10.10.0.07", - "user": "ipmi_u", "password": "pass1234" - } - - obminfo8 = { - "type": api_nodename + 'mock', "host": "10.10.0.08", - "user": "ipmi_u", "password": "pass1234" - } - - requests.put(url_node + 'node-01', data=json.dumps({"obm": obminfo1})) - requests.put(url_node + 'node-02', data=json.dumps({"obm": obminfo2})) - requests.put(url_node + 'node-03', data=json.dumps({"obm": obminfo3})) - requests.put(url_node + 'node-04', data=json.dumps({"obm": obminfo4})) - requests.put(url_node + 'node-05', data=json.dumps({"obm": obminfo5})) - requests.put(url_node + 'node-06', data=json.dumps({"obm": obminfo6})) - requests.put(url_node + 'node-07', data=json.dumps({"obm": obminfo7})) - requests.put(url_node + 'node-08', data=json.dumps({"obm": obminfo8})) - # Adding nics to nodes - for i in range(1, 8): + for i in range(1, 9): + obminfo = { + "type": api_nodename + 'ipmi', "host": "10.10.0.0"+repr(i), + "user": "ipmi_u", "password": "pass1234" + } + requests.put(url_node + 'node-0'+repr(i), + data=json.dumps({"obm": obminfo})) requests.put(url_node + 'node-0' + repr(i) + '/nic/eth0', data=json.dumps({"macaddr": "aa:bb:cc:dd:ee:0" + repr(i)}) ) @@ -359,10 +314,12 @@ def test_power_off(self): assert result is None def test_node_add_nic(self): + C.node.remove_nic('node-08', 'eth0') result = C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') assert result is None def test_node_add_duplicate_nic(self): + C.node.remove_nic('node-08', 'eth0') C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') with pytest.raises(errors.DuplicateError): C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') @@ -372,12 +329,10 @@ def test_nosuch_node_add_nic(self): C.node.add_nic('abcd', 'eth0', 'aa:bb:cc:dd:ee:ff') def test_remove_nic(self): - C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') result = C.node.remove_nic('node-08', 'eth0') assert result is None def test_remove_duplicate_nic(self): - C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') C.node.remove_nic('node-08', 'eth0') with pytest.raises(errors.NotFoundError): C.node.remove_nic('node-08', 'eth0') @@ -469,20 +424,20 @@ def test_project_connect_node_nosuchobject(self): with pytest.raises(errors.NotFoundError): C.project.connect('no-such-project', 'node-06') - def test_project_disconnect_node(self): - """ Test for correctly disconnecting node from project.""" + def test_project_detach_node(self): + """ Test for correctly detaching node from project.""" C.project.create('abcd') C.project.connect('abcd', 'node-07') - result = C.project.disconnect('abcd', 'node-07') + result = C.project.detach('abcd', 'node-07') assert result is None - def test_project_disconnect_node_nosuchobject(self): - """ Test for while disconnecting node from project.""" + def test_project_detach_node_nosuchobject(self): + """ Test for while detaching node from project.""" C.project.create('abcd') with pytest.raises(errors.NotFoundError): - C.project.disconnect('abcd', 'no-such-node') + C.project.detach('abcd', 'no-such-node') with pytest.raises(errors.NotFoundError): - C.project.disconnect('no-such-project', 'node-06') + C.project.detach('no-such-project', 'node-06') @pytest.mark.usefixtures("create_setup") @@ -533,21 +488,18 @@ def test_port_connect_nic(self): def test_port_connect_nic_error(self): C.port.register('dell-01', 'abcd') - C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:08') C.port.connect_nic('dell-01', 'abcd', 'node-08', 'eth0') with pytest.raises(errors.DuplicateError): C.port.connect_nic('dell-01', 'abcd', 'node-08', 'eth0') def test_port_detach_nic(self): C.port.register('dell-01', 'gi1/0/11') - C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:08') C.port.connect_nic('dell-01', 'gi1/0/11', 'node-08', 'eth0') result = C.port.detach_nic('dell-01', 'gi1/0/11') assert result is None def test_port_detach_nic_error(self): C.port.register('dell-01', 'gi1/0/11') - C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:08') C.port.connect_nic('dell-01', 'gi1/0/11', 'node-08', 'eth0') C.port.detach_nic('dell-01', 'gi1/0/11') with pytest.raises(errors.NotFoundError): From 5e30397b83cc202552be03e446217c73ce0e6403 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 1 Feb 2017 13:00:49 -0500 Subject: [PATCH 43/71] fixing pep8 issues --- tests/unit/client.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index d56ed2c5..706dd618 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -160,16 +160,18 @@ def populate_server(): url_node = 'http://127.0.0.1:8888/node/' api_nodename = 'http://schema.massopencloud.org/haas/v0/obm/' - for i in range(1, 9): obminfo = { "type": api_nodename + 'ipmi', "host": "10.10.0.0"+repr(i), "user": "ipmi_u", "password": "pass1234" } - requests.put(url_node + 'node-0'+repr(i), - data=json.dumps({"obm": obminfo})) - requests.put(url_node + 'node-0' + repr(i) + '/nic/eth0', - data=json.dumps({"macaddr": "aa:bb:cc:dd:ee:0" + repr(i)}) + requests.put( + url_node + 'node-0'+repr(i), data=json.dumps({"obm": obminfo}) + ) + requests.put( + url_node + 'node-0' + repr(i) + '/nic/eth0', data=json.dumps( + {"macaddr": "aa:bb:cc:dd:ee:0" + repr(i)} + ) ) # Adding Projects proj-01 - proj-03 From b45ec800e66661a881d6097b302b612b8a00260c Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Thu, 16 Feb 2017 12:43:45 -0500 Subject: [PATCH 44/71] Includes code refactored to reduce verbosity as recommended by @zenhack -- Modifications affect only project related calls in client library and cli. -- Fixes unit tests appropriately -- Reverts back to the `haas/errors.py` as in master as the unique status_codes are no longer relevant --- haas/cli.py | 52 ++++++++++++----------- haas/client/base.py | 30 +++++++++++++ haas/client/errors.py | 4 ++ haas/client/project.py | 96 ++++++++++++------------------------------ haas/errors.py | 9 +--- tests/unit/client.py | 14 +++--- 6 files changed, 97 insertions(+), 108 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 32d91859..0ad1cd5d 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -227,6 +227,13 @@ class FailedAPICallException(Exception): pass +def check_clientlib_response(fun, *fun_args): + try: + return fun(*fun_args) + except Exception as e: + sys.stderr.write('Error: %s\n' % e.message) + + def check_status_code(response): if response.status_code < 200 or response.status_code >= 300: sys.stderr.write('Unexpected status code: %d\n' % response.status_code) @@ -378,10 +385,11 @@ def list_projects(): @cmd def user_add_project(user, project): """Add to """ - try: - C.user.grant_access(user, project) - except (errors.NotFoundError, errors.DuplicateError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(C.user.grant_access, user, project) +# try: +# C.user.grant_access(user, project) +# except (errors.NotFoundError, errors.DuplicateError) as e: +# sys.stderr.write('Error: %s\n' % e.message) @cmd @@ -414,19 +422,13 @@ def network_revoke_project_access(project, network): @cmd def project_create(project): """Create a """ - try: - C.project.create(project) - except (errors.DuplicateError, errors.AuthenticationError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(C.project.create, project) @cmd def project_delete(project): """Delete """ - try: - C.project.delete(project) - except (errors.NotFoundError, errors.AuthenticationError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(C.project.delete, project) @cmd @@ -447,19 +449,13 @@ def headnode_delete(headnode): @cmd def project_connect_node(project, node): """Connect to """ - try: - C.project.connect(project, node) - except (errors.NotFoundError, errors.BlockedError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(C.project.connect, project, node) @cmd def project_detach_node(project, node): """Detach from """ - try: - C.project.disconnect(project, node) - except (errors.NotFoundError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(C.project.detach, project, node) @cmd @@ -752,17 +748,25 @@ def list_nodes(is_free): @cmd def list_project_nodes(project): """List all nodes attached to a """ - q = C.project.nodes_in(project) - sys.stdout.write('Nodes allocated to %s: ' % project + " ".join(q) + '\n') + try: + q = C.project.nodes_in(project) + sys.stdout.write( + 'Nodes allocated to %s: ' % project + " ".join(q) + '\n' + ) + except Exception as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd def list_project_networks(project): """List all networks attached to a """ - q = C.project.networks_in(project) - sys.stdout.write( + try: + q = C.project.networks_in(project) + sys.stdout.write( "Networks allocated to {}\t: {}\n".format(project, " ".join(q)) ) + except Exception as e: + sys.stderr.write('Error: %s\n' % e.message) @cmd diff --git a/haas/client/base.py b/haas/client/base.py index da2cb359..afbc2963 100644 --- a/haas/client/base.py +++ b/haas/client/base.py @@ -7,6 +7,10 @@ from urlparse import urljoin +def check_response(response): + return response.json() + + class ClientBase(object): """ Main class which contains all the methods to -- ensure input complies to API requisites @@ -38,3 +42,29 @@ def object_url(self, *args): rel = "/".join(args) url = urljoin(self.endpoint, rel) return url + + def check_status_code(self, response): + if response.status_code < 200 or response.status_code >= 300: + sys.stderr.write( + 'Unexpected status code: %d\n' % response.status_code + ) + sys.stderr.write('Response text: \n') + sys.stderr.write(response.text + "\n") + raise FailedAPICallException() + else: + sys.stdout.write(response.text + "\n") + + def check_response(self, response): + self.res = response + if self.res.ok: + if response.request.method == 'GET': + return self.res.json() + else: + return # For methods PUT, POST, DELETE + else: + e = self.res.json() + raise errors.FailedAPICallException(e['msg']) + + def do_get(self, url, params=None): + self.url = url + self.check_response(self.s.get(url)) diff --git a/haas/client/errors.py b/haas/client/errors.py index 021a667a..45befc52 100644 --- a/haas/client/errors.py +++ b/haas/client/errors.py @@ -16,3 +16,7 @@ class BlockedError(Exception): class ProjectMismatchError(Exception): pass + + +class FailedAPICallException(Exception): + pass diff --git a/haas/client/project.py b/haas/client/project.py index 4b8e80ef..99c4ee53 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -1,5 +1,5 @@ import json -from haas.client.base import ClientBase +from haas.client.base import ClientBase, check_response from haas.client import errors @@ -9,100 +9,56 @@ class Project(ClientBase): def list(self): """ Lists all projects under HIL """ - url = self.object_url('/projects') - q = self.s.get(url) - if q.ok: - return q.json() - elif q.status_code == 401: - raise errors.AuthenticationError( - "Make sure credentials match " - "chosen authentication backend." - ) + + self.url = self.object_url('/projects') + return self.check_response(self.s.get(self.url)) def nodes_in(self, project_name): """ Lists nodes allocated to project """ self.project_name = project_name - url = self.object_url('project', self.project_name, 'nodes') - q = self.s.get(url) - if q.ok: - return q.json() - elif q.status_code == 401: - raise errors.AuthenticationError("Invalid credentials.") - elif q.status_code == 404: - raise errors.NotFoundError( - "Project %s does not exist." % self.project_name - ) + self.url = self.object_url('project', self.project_name, 'nodes') + return self.check_response(self.s.get(self.url)) def networks_in(self, project_name): """ Lists nodes allocated to project """ self.project_name = project_name - url = self.object_url('project', self.project_name, 'networks') - q = self.s.get(url) - if q.ok: - return q.json() - elif q.status_code == 401: - raise errors.AuthenticationError("Invalid credentials.") - elif q.status_code == 404: - raise errors.NotFoundError( - "Project %s does not exist." % self.project_name - ) + self.url = self.object_url( + 'project', self.project_name, 'networks' + ) + return self.check_response(self.s.get(self.url)) def create(self, project_name): """ Creates a project named """ self.project_name = project_name - url = self.object_url('project', self.project_name) - q = self.s.put(url) - if q.ok: - return - elif q.status_code == 401: - raise errors.AuthenticationError("Invalid credentials.") - elif q.status_code == 409: - raise errors.DuplicateError("Project Name not unique.") + self.url = self.object_url('project', self.project_name) + return self.check_response(self.s.put(self.url)) def delete(self, project_name): """ Deletes a project named """ self.project_name = project_name - url = self.object_url('project', self.project_name) - q = self.s.delete(url) - if q.ok: - return - elif q.status_code == 401: - raise errors.AuthenticationError("Invalid credentials.") - elif q.status_code == 404: - raise errors.NotFoundError( - "Deletion failed. Project does not exist." - ) + self.url = self.object_url('project', self.project_name) + return self.check_response(self.s.delete(self.url)) def connect(self, project_name, node_name): """ Adds a node to a project. """ self.project_name = project_name self.node_name = node_name - url = self.object_url('project', self.project_name, 'connect_node') - payload = json.dumps({'node': self.node_name}) - q = self.s.post(url, data=payload) - if q.ok: - return - elif q.status_code == 423: - raise errors.BlockedError( - "Not a free node. Only free nodes can be added to a " - "project. " - ) - elif q.status_code == 404: - raise errors.NotFoundError("Node or project does not exist.") + self.url = self.object_url( + 'project', self.project_name, 'connect_node' + ) + self.payload = json.dumps({'node': self.node_name}) + return self.check_response( + self.s.post(self.url, data=self.payload) + ) def detach(self, project_name, node_name): """ Adds a node to a project. """ self.project_name = project_name self.node_name = node_name - url = self.object_url('project', project_name, 'detach_node') - payload = json.dumps({'node': node_name}) - q = self.s.post(url, data=payload) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError( - "Node or Project does not exist or " - "Node not owned by project" - ) + self.url = self.object_url('project', project_name, 'detach_node') + self.payload = json.dumps({'node': node_name}) + return self.check_response( + self.s.post(self.url, data=self.payload) + ) diff --git a/haas/errors.py b/haas/errors.py index 59989e45..d2f7285f 100644 --- a/haas/errors.py +++ b/haas/errors.py @@ -80,9 +80,7 @@ class ProjectMismatchError(APIError): """An exception indicating that the resources given don't belong to the same project. """ - status_code = 412 # was 409. 412 is appropriate here. - # For description read, - # http://www.restpatterns.org/HTTP_Status_Codes/412_-_Precondition_Failed + status_code = 409 # Conflict class AuthorizationError(APIError): @@ -94,10 +92,7 @@ class BlockedError(APIError): some other change. For example, deletion is blocked until the components are deleted, and possibly until the dirty flag is cleared as well. """ - status_code = 423 # was 409, - # This seems to be the closest code for this situation - # For description read, - # http://www.restpatterns.org/HTTP_Status_Codes/423_-_Locked + status_code = 409 # Conflict class IllegalStateError(APIError): diff --git a/tests/unit/client.py b/tests/unit/client.py index 706dd618..889ee708 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -391,7 +391,7 @@ def test_project_create(self): def test_duplicate_project_create(self): """ test for catching duplicate name while creating new project. """ C.project.create('dummy-01') - with pytest.raises(errors.DuplicateError): + with pytest.raises(Exception): C.project.create('dummy-01') def test_project_delete(self): @@ -402,7 +402,7 @@ def test_project_delete(self): def test_error_project_delete(self): """ test to capture error condition in project delete. """ - with pytest.raises(errors.NotFoundError): + with pytest.raises(Exception): C.project.delete('dummy-03') def test_project_connect_node(self): @@ -415,15 +415,15 @@ def test_project_connect_node_duplicate(self): """ test for erronous reconnecting node to project. """ C.project.create('abcd') C.project.connect('abcd', 'node-08') - with pytest.raises(errors.BlockedError): + with pytest.raises(Exception): C.project.connect('abcd', 'node-08') def test_project_connect_node_nosuchobject(self): """ test for connecting no such node or project """ C.project.create('abcd') - with pytest.raises(errors.NotFoundError): + with pytest.raises(Exception): C.project.connect('abcd', 'no-such-node') - with pytest.raises(errors.NotFoundError): + with pytest.raises(Exception): C.project.connect('no-such-project', 'node-06') def test_project_detach_node(self): @@ -436,9 +436,9 @@ def test_project_detach_node(self): def test_project_detach_node_nosuchobject(self): """ Test for while detaching node from project.""" C.project.create('abcd') - with pytest.raises(errors.NotFoundError): + with pytest.raises(Exception): C.project.detach('abcd', 'no-such-node') - with pytest.raises(errors.NotFoundError): + with pytest.raises(Exception): C.project.detach('no-such-project', 'node-06') From d20c55ff6675b19c8099e21bafb2518c38e6dec9 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Thu, 16 Feb 2017 12:52:52 -0500 Subject: [PATCH 45/71] removing non-essential code --- haas/client/base.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/haas/client/base.py b/haas/client/base.py index afbc2963..f929d801 100644 --- a/haas/client/base.py +++ b/haas/client/base.py @@ -7,10 +7,6 @@ from urlparse import urljoin -def check_response(response): - return response.json() - - class ClientBase(object): """ Main class which contains all the methods to -- ensure input complies to API requisites @@ -43,17 +39,6 @@ def object_url(self, *args): url = urljoin(self.endpoint, rel) return url - def check_status_code(self, response): - if response.status_code < 200 or response.status_code >= 300: - sys.stderr.write( - 'Unexpected status code: %d\n' % response.status_code - ) - sys.stderr.write('Response text: \n') - sys.stderr.write(response.text + "\n") - raise FailedAPICallException() - else: - sys.stdout.write(response.text + "\n") - def check_response(self, response): self.res = response if self.res.ok: From adc94d0ae844544a1dddf66f291ca0bfd518d51a Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Thu, 16 Feb 2017 13:11:16 -0500 Subject: [PATCH 46/71] removes import that is not required anymore --- haas/client/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/haas/client/project.py b/haas/client/project.py index 99c4ee53..01c89aab 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -1,5 +1,5 @@ import json -from haas.client.base import ClientBase, check_response +from haas.client.base import ClientBase from haas.client import errors From 290e1503c3f98b415faac5668afcc23eef854ff0 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Sat, 18 Feb 2017 10:16:23 -0500 Subject: [PATCH 47/71] Refactors cli.py to reduce repetitive code --- haas/cli.py | 175 +++++++++++++++++----------------------------------- 1 file changed, 58 insertions(+), 117 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index a4f80670..2bd48981 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -227,7 +227,14 @@ class FailedAPICallException(Exception): pass -def check_clientlib_response(fun, *fun_args): +def check_clientlib_response(fun): + try: + return fun() + except Exception as e: + sys.stderr.write('Error: %s\n' % e.message) + + +def clientlib_response(fun, *fun_args): try: return fun(*fun_args) except Exception as e: @@ -333,102 +340,84 @@ def user_create(username, password, is_admin): may be either "admin" or "regular", and determines whether the user has administrative priveledges. """ - try: - C.user.create(username, password, is_admin) - except (errors.DuplicateError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response( + lambda: C.user.create(username, password, is_admin) + ) @cmd def network_create(network, owner, access, net_id): """Create a link-layer . See docs/networks.md for details""" - try: - C.network.create(network, owner, access, net_id) - except (errors.DuplicateError, errors.NotFoundError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response( + lambda: C.network.create(network, owner, access, net_id) + ) @cmd def network_create_simple(network, project): """Create owned by project. Specific case of network_create""" - try: - C.network.create(network, project, project, "") - except (errors.DuplicateError, errors.NotFoundError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response( + lambda: C.network.create(network, project, project, "") + ) @cmd def network_delete(network): """Delete a """ - try: - C.network.delete(network) - except (errors.NotFoundError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response( + lambda: C.network.delete(network) + ) @cmd def user_delete(username): """Delete the user """ - try: - C.user.delete(username) - except (errors.NotFoundError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response( + lambda: C.user.delete(username) + ) @cmd def list_projects(): """List all projects""" - q = C.project.list() + q = check_clientlib_response(lambda: C.project.list()) sys.stdout.write('%s Projects : ' % len(q) + " ".join(q) + '\n') @cmd def user_add_project(user, project): """Add to """ - check_clientlib_response(C.user.grant_access, user, project) -# try: -# C.user.grant_access(user, project) -# except (errors.NotFoundError, errors.DuplicateError) as e: -# sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.user.grant_access(user, project)) @cmd def user_remove_project(user, project): """Remove from """ - try: - C.user.remove_access(user, project) - except (errors.NotFoundError, errors.DuplicateError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.user.remove_access(user, project)) @cmd def network_grant_project_access(project, network): """Add to access""" - try: - C.network.grant_access(project, network) - except (errors.NotFoundError, errors.DuplicateError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.network.grant_access(project, network)) @cmd def network_revoke_project_access(project, network): """Remove from access""" - try: - C.network.revoke_access(project, network) - except (errors.NotFoundError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.network.revoke_access(project, network)) @cmd def project_create(project): """Create a """ - check_clientlib_response(C.project.create, project) + check_clientlib_response(lambda: C.project.create(project)) @cmd def project_delete(project): """Delete """ - check_clientlib_response(C.project.delete, project) + check_clientlib_response(lambda: C.project.delete(project)) @cmd @@ -506,28 +495,19 @@ def node_register(node, subtype, *args): @cmd def node_delete(node): """Delete """ - try: - C.node.delete(node) - except (errors.NotFoundError, errors.BlockedError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.node.delete(node)) @cmd def node_power_cycle(node): """Power cycle """ - try: - C.node.power_cycle(node) - except (errors.NotFoundError, errors.BlockedError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.node.power_cycle(node)) @cmd def node_power_off(node): """Power off """ - try: - C.node.power_off(node) - except (errors.NotFoundError, errors.BlockedError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.node.power_off(node)) @cmd @@ -535,19 +515,13 @@ def node_register_nic(node, nic, macaddr): """ Register existence of a with the given on the given """ - try: - C.node.add_nic(node, nic, macaddr) - except (errors.NotFoundError, errors.DuplicateError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.node.add_nic(node, nic, macaddr)) @cmd def node_delete_nic(node, nic): """Delete a on a """ - try: - C.node.remove_nic(node, nic) - except(errors.NotFoundError, errors.BlockedError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_client_lib(lambda: C.node.remove_nic(node, nic)) @cmd @@ -567,19 +541,15 @@ def headnode_delete_hnic(headnode, nic): @cmd def node_connect_network(node, nic, network, channel): """Connect to on given and """ - try: - C.node.connect_network(node, nic, network, channel) - except(errors.NotFoundError, errors.DuplicateError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_client_lib( + lambda: C.node.connect_network(node, nic, network,channel) + ) @cmd def node_detach_network(node, nic, network): """Detach from the given on the given """ - try: - C.node.detach_network(node, nic, network) - except(errors.NotFoundError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.node.detach_network(node, nic, network)) @cmd @@ -678,56 +648,38 @@ def switch_register(switch, subtype, *args): @cmd def switch_delete(switch): """Delete a """ - try: - C.switch.delete(switch) - except(errors.NotFoundError, BlockedError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.switch.delete(switch)) @cmd def list_switches(): """List all switches""" - q = C.switch.list() + q = check_clientlib_response(lambda: C.switch.list()) sys.stdout.write('%s switches : ' % len(q) + " ".join(q) + '\n') @cmd def port_register(switch, port): """Register a with """ - try: - C.port.register(switch, port) - except(errors.DuplicateError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.port.register(switch, port)) @cmd def port_delete(switch, port): """Delete a from a """ - try: - C.port.delete(switch, port) - except(errors.NotFoundError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.port.delete(switch, port)) @cmd def port_connect_nic(switch, port, node, nic): """Connect a on a to a on a """ - try: - C.port.connect_nic(switch, port, node, nic) - except(errors.DuplicateError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.port.connect_nic(switch, port, node, nic)) @cmd def port_detach_nic(switch, port): """Detach a on a from whatever's connected to it""" -# url = object_url('switch', switch, 'port', port, 'detach_nic') -# do_post(url) - - try: - C.port.detach_nic(switch, port) - except(errors.NotFoundError) as e: - sys.stderr.write('Error: %s\n' % e.message) + check_clientlib_response(lambda: C.port.detach_nic(switch, port)) @cmd @@ -750,7 +702,7 @@ def list_nodes(is_free): may be either "all" or "free", and determines whether to list all nodes or all free nodes. """ - q = C.node.list(is_free) + q = check_clientlib_response(lambda: C.node.list(is_free)) if is_free == 'all': sys.stdout.write('All nodes {}\t: {}\n'.format(len(q), " ".join(q))) elif is_free == 'free': @@ -762,42 +714,31 @@ def list_nodes(is_free): @cmd def list_project_nodes(project): """List all nodes attached to a """ - try: - q = C.project.nodes_in(project) - sys.stdout.write( - 'Nodes allocated to %s: ' % project + " ".join(q) + '\n' - ) - except Exception as e: - sys.stderr.write('Error: %s\n' % e.message) + q = check_clientlib_response(lambda: C.project.nodes_in(project)) + sys.stdout.write('Nodes allocated to %s: ' % project + " ".join(q) + '\n') @cmd def list_project_networks(project): """List all networks attached to a """ - try: - q = C.project.networks_in(project) - sys.stdout.write( + q = check_clientlib_response(lambda: C.project.networks_in(project)) + sys.stdout.write( "Networks allocated to {}\t: {}\n".format(project, " ".join(q)) ) - except Exception as e: - sys.stderr.write('Error: %s\n' % e.message) @cmd def show_switch(switch): """Display information about """ - try: - q = C.switch.show(switch) - for item in q.items(): - sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) - except(errors.NotFoundError) as e: - sys.stderr.write('Error: %s\n' % e.message) + q = check_clientlib_response(lambda: C.switch.show(switch)) + for item in q.items(): + sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) @cmd def list_networks(): """List all networks""" - q = C.network.list() + q = check_clientlib_response(lambda: C.network.list()) for item in q.items(): sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) @@ -805,7 +746,7 @@ def list_networks(): @cmd def show_network(network): """Display information about """ - q = C.network.show(network) + q = check_clientlib_response(lambda: C.network.show(network)) for item in q.items(): sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) @@ -815,7 +756,7 @@ def show_node(node): """Display information about a FIXME: Recursion should be implemented to the output. """ - q = C.node.show(node) + q = check_clientlib_response(lambda: C.node.show(node)) for item in q.items(): sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) @@ -851,13 +792,13 @@ def show_console(node): @cmd def start_console(node): """Start logging console output from """ - q = C.node.start_console(node) + q = check_clientlib_response(lambda: C.node.start_console(node)) @cmd def stop_console(node): """Stop logging console output from and delete the log""" - q = C.node.stop_console(node) + q = check_clientlib_response(lambda: C.node.stop_console(node)) @cmd From a54ce5e381fa63dc8ac123d1b35c2b19ce4b55b8 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Sat, 18 Feb 2017 19:48:25 -0500 Subject: [PATCH 48/71] Fixes the broken test due to recent merging with the master --- tests/unit/client.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 889ee708..16180069 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -301,11 +301,16 @@ def test_list_nodes_all(self): def test_show_node(self): result = C.node.show('node-07') assert result == { - u'project': None, - u'nics': [{u'macaddr': u'aa:bb:cc:dd:ee:07', - u'networks': {}, u'label': u'eth0'}], - u'name': u'node-07' - } + u'metadata': {}, + u'project': None, + u'nics': [ + { + u'macaddr': u'aa:bb:cc:dd:ee:07', + u'networks': {}, u'label': u'eth0' + } + ], + u'name': u'node-07' + } def test_power_cycle(self): result = C.node.power_cycle('node-07') From b1b3e97d53ebbf4cff6664bc7c7c167f4aec696d Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Sat, 18 Feb 2017 19:48:57 -0500 Subject: [PATCH 49/71] Fixes the broken test due to recent merging with the master --- haas/client/base.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/haas/client/base.py b/haas/client/base.py index f929d801..d03a11db 100644 --- a/haas/client/base.py +++ b/haas/client/base.py @@ -49,7 +49,3 @@ def check_response(self, response): else: e = self.res.json() raise errors.FailedAPICallException(e['msg']) - - def do_get(self, url, params=None): - self.url = url - self.check_response(self.s.get(url)) From de9ddeb48e46a63e0a084afc0bf80e3959436608 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Sun, 19 Feb 2017 01:35:01 -0500 Subject: [PATCH 50/71] Refactors client library code to reduce code verbosity. Adds tests for network and user calls Changes the testing setup to allow user authentication. Was needed for user related tests --- haas/client/base.py | 4 +- haas/client/network.py | 63 +-------- haas/client/node.py | 112 ++-------------- haas/client/switch.py | 56 +------- haas/client/user.py | 41 +----- tests/unit/client.py | 292 ++++++++++++++++++++++++++++++----------- 6 files changed, 246 insertions(+), 322 deletions(-) diff --git a/haas/client/base.py b/haas/client/base.py index d03a11db..f3f5f752 100644 --- a/haas/client/base.py +++ b/haas/client/base.py @@ -44,8 +44,8 @@ def check_response(self, response): if self.res.ok: if response.request.method == 'GET': return self.res.json() - else: - return # For methods PUT, POST, DELETE + else: # For methods PUT, POST, DELETE + return else: e = self.res.json() raise errors.FailedAPICallException(e['msg']) diff --git a/haas/client/network.py b/haas/client/network.py index 56ea46b8..b12ae8b8 100644 --- a/haas/client/network.py +++ b/haas/client/network.py @@ -10,31 +10,13 @@ class Network(ClientBase): def list(self): """ Lists all projects under HIL """ url = self.object_url('networks') - q = self.s.get(url) - if q.ok: - return q.json() - elif q.status_code == 401: - raise errors.AuthenticationError( - "Make sure credentials match " - "chosen authentication backend." - ) + return self.check_response(self.s.get(url)) def show(self, network): """ Shows attributes of a network. """ self.network = network url = self.object_url('network', self.network) - q = self.s.get(url) - if q.ok: - return q.json() - elif q.status_code == 401: - raise errors.AuthenticationError( - "Make sure credential match." - "chosen authentication backend." - ) - elif q.status_code == 404: - raise errors.NotFoundError( - "Network does not exist." - ) + return self.check_response(self.s.get(url)) def create(self, network, owner, access, net_id): """ Create a link-layer . See docs/networks.md for @@ -51,30 +33,14 @@ def create(self, network, owner, access, net_id): 'owner': self.owner, 'access': self.access, 'net_id': self.net_id }) - q = self.s.put(url, data=payload) - if q.ok: - return - elif q.status_code == 409: - raise errors.DuplicateError( - "Network name already exists. " - ) - elif q.status_code == 404: - raise errors.NotFoundError( - "Operation failed. Missing Parameters. " - ) + return self.check_response(self.s.put(url, data=payload)) def delete(self, network): """ Delete a . """ self.network = network url = self.object_url('network', self.network) - q = self.s.delete(url) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError( - "Operation failed. No such network. " - ) + return self.check_response(self.s.delete(url)) def grant_access(self, project, network): """ Grants access to . """ @@ -85,17 +51,7 @@ def grant_access(self, project, network): url = self.object_url( 'network', self.network, 'access', self.project ) - q = self.s.put(url) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError( - "Operation failed. Resource does not exist. " - ) - elif q.status_code == 409: - raise errors.DuplicateError( - "Access relationship already exists. " - ) + return self.check_response(self.s.put(url)) def revoke_access(self, project, network): """ Removes access of from . """ @@ -106,11 +62,4 @@ def revoke_access(self, project, network): url = self.object_url( 'network', self.network, 'access', self.project ) - q = self.s.delete(url) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError( - "Operation failed. Resource or relationship " - "does not exist. " - ) + return self.check_response(self.s.delete(url)) diff --git a/haas/client/node.py b/haas/client/node.py index 6b6ffc52..30f56381 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -12,13 +12,7 @@ def list(self, is_free): """ List all nodes that HIL manages """ self.is_free = is_free url = self.object_url('nodes', self.is_free) - q = self.s.get(url) - if q.ok: - return q.json() - elif q.status_code == 401: - raise errors.AuthenticationError( - "Make sure credentials match chosen authentication backend." - ) + return self.check_response(self.s.get(url)) def show(self, node_name): """ Shows attributes of a given node """ @@ -26,7 +20,7 @@ def show(self, node_name): self.node_name = node_name url = self.object_url('node', self.node_name) q = self.s.get(url) - return q.json() + return self.check_response(self.s.get(url)) def register(self, node, subtype, *args): """ Register a node with appropriate OBM driver. """ @@ -49,53 +43,19 @@ def delete(self, node_name): """ Deletes the node from database. """ self.node_name = node_name url = self.object_url('node', self.node_name) - q = self.s.delete(url) - if q.ok: - return - elif q.status_code == 409: - raise errors.BlockedError( - "Make sure all nics are removed before deleting the node" - ) - elif q.status_code == 404: - raise errors.NotFoundError( - "No such node exist. Nothing to delete." - ) + return check_response(self.s.delete(url)) def power_cycle(self, node_name): """ Power cycles the """ self.node_name = node_name url = self.object_url('node', node_name, 'power_cycle') - q = self.s.post(url) - if q.ok: - return - elif q.status_code == 409: - raise errors.BlockedError( - "Operation blocked by other pending operations" - ) - elif q.status_code == 500: - raise errors.NotFoundError( - "Operation Failed. This is a server-side problem. " - "Contact your HIL Administrator. " - ) + return self.check_response(self.s.post(url)) def power_off(self, node_name): """ Power offs the """ self.node_name = node_name url = self.object_url('node', self.node_name, 'power_off') - q = self.s.post(url) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError("Node not found.") - elif q.status_code == 409: - raise errors.BlockedError( - "Operation blocked by other pending operations" - ) - elif q.status_code == 500: - raise errors.NotFoundError( - "Operation Failed. This is a server-side problem. " - "Contact your HIL Administrator. " - ) + return self.check_response(self.s.post(url)) def add_nic(self, node_name, nic_name, macaddr): """ adds a to """ @@ -104,34 +64,14 @@ def add_nic(self, node_name, nic_name, macaddr): self.macaddr = macaddr url = self.object_url('node', self.node_name, 'nic', self.nic_name) payload = json.dumps({'macaddr': self.macaddr}) - q = self.s.put(url, data=payload) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError( - "Nic cannot be added. Node does not exist." - ) - elif q.status_code == 409: - raise errors.DuplicateError( - "Nic already exists." - ) + return self.check_response(self.s.put(url, data=payload)) def remove_nic(self, node_name, nic_name): """ remove a from """ self.node_name = node_name self.nic_name = nic_name url = self.object_url('node', self.node_name, 'nic', self.nic_name) - q = self.s.delete(url) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError( - "Nic not found. Nothing to delete." - ) - elif q.status_code == 409: - raise errors.BlockedError( - "Cannot delete nic, diconnect it from network first" - ) + return self.check_response(self.s.delete(url)) def connect_network(self, node, nic, network, channel): """ Connect to on given and """ @@ -147,23 +87,7 @@ def connect_network(self, node, nic, network, channel): payload = json.dumps({ 'network': self.network, 'channel': self.channel }) - q = self.s.post(url, payload) - if q.ok: - return - if q.status_code == 404: - raise errors.NotFoundError( - "Resource or relationship does not exist. " - ) - if q.status_code == 409: - raise errors.DuplicateError("A network is already attached.") - if q.status_code == 412: - raise errors.ProjectMismatchError( - "Project does not have access to either resource. " - ) - if q.status_code == 423: - raise errors.BlockedError( - "Networking operations pending on this nic. " - ) + return self.check_response(self.s.post(url, payload)) def detach_network(self, node, nic, network): """ Disconnect from on the given . """ @@ -176,17 +100,7 @@ def detach_network(self, node, nic, network): 'node', self.node, 'nic', self.nic, 'detach_network' ) payload = json.dumps({'network': self.network}) - q = self.s.post(url, payload) - if q.ok: - return - if q.status_code == 400: - raise errors.NotFoundError( - "No such network attached to the nic. " - ) - if q.status_code == 423: - raise errors.BlockedError( - "Networking operations pending on this nic. " - ) + return self.check_response(self.s.post(url, payload)) def show_console(self, node): """ Display console log for """ @@ -196,14 +110,10 @@ def start_console(self, node): """ Start logging console output from """ self.node = node url = self.object_url('node', self.node, 'console') - q = self.s.put(url) - if q.ok: - return + return self.check_response(self.s.put(url)) def stop_console(self, node): """ Stop logging console output from and delete the log""" self.node = node url = self.object_url('node', self.node, 'console') - q = self.s.delete(url) - if q.ok: - return + return self.check_response(self.s.delete(url)) diff --git a/haas/client/switch.py b/haas/client/switch.py index 3f91929f..e1e7809b 100644 --- a/haas/client/switch.py +++ b/haas/client/switch.py @@ -10,14 +10,7 @@ class Switch(ClientBase): def list(self): """ List all nodes that HIL manages """ url = self.object_url('/switches') - q = self.s.get(url) - if q.ok: - return q.json() - elif q.status_code == 401: - raise errors.AuthenticationError( - "Make sure credentials match " - "chosen authentication backend." - ) + return self.check_response(self.s.get(url)) def register(self, switch, subtype, *args): """ Registers a switch with name and @@ -33,31 +26,14 @@ def register(self, switch, subtype, *args): def delete(self, switch): self.switch = switch url = self.object_url('switch', self.switch) - q = self.s.delete(url) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError( - " Operation failed. Resource does not exist." - ) - elif q.status_code == 409: - raise errors.BlockedError( - " Operation failed. Cannot delete switch." - " Delete its ports first." - ) + return self.check_response(self.s.delete(url)) def show(self, switch): """ Shows attributes of . """ self.switch = switch url = self.object_url('switch', self.switch) - q = self.s.get(url) - if q.ok: - return q.json() - elif q.status_code == 404: - raise errors.NotFoundError( - "Operation failed. No such switch." - ) + return self.check_response(self.s.get(url)) class Port(ClientBase): @@ -69,11 +45,7 @@ def register(self, switch, port): self.port = port url = self.object_url('switch', switch, 'port', port) - q = self.s.put(url) - if q.ok: - return - elif q.status_code == 409: - raise errors.DuplicateError("Port name not unique.") + return self.check_response(self.s.put(url)) def delete(self, switch, port): """ Deletes information of the for """ @@ -81,11 +53,7 @@ def delete(self, switch, port): self.port = port url = self.object_url('switch', switch, 'port', port) - q = self.s.delete(url) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError("Port name not found.") + return self.check_response(self.s.delete(url)) def connect_nic(self, switch, port, node, nic): """ Connects of to of . """ @@ -96,21 +64,11 @@ def connect_nic(self, switch, port, node, nic): url = self.object_url('switch', switch, 'port', port, 'connect_nic') payload = json.dumps({'node': self.node, 'nic': self.nic}) - q = self.s.post(url, payload) - if q.ok: - return - elif q.status_code == 409: - raise errors.DuplicateError("Port or Nic already connected.") + return self.check_response(self.s.post(url, payload)) def detach_nic(self, switch, port): """" Detaches of . """ self.switch = switch self.port = port url = self.object_url('switch', switch, 'port', port, 'detach_nic') - q = self.s.post(url) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError( - "Operation Failed. The relationship does not exist. " - ) + return self.check_response(self.s.post(url)) diff --git a/haas/client/user.py b/haas/client/user.py index 33898d90..6e287f69 100644 --- a/haas/client/user.py +++ b/haas/client/user.py @@ -25,27 +25,13 @@ def create(self, username, password, privilege): payload = json.dumps({ 'password': self.password, 'is_admin': privilege == 'admin', }) - q = self.s.put(url, data=payload) - if q.ok: - return - elif q.status_code == 401: - raise errors.AuthenticationError( - "You do not have rights for user creation" - ) - elif q.status_code == 409: - raise errors.DuplicateError("User already exists.") + return self.check_response(self.s.put(url, data=payload)) def delete(self, username): """Deletes the user . """ self.username = username url = self.object_url('/auth/basic/user', self.username) - q = self.s.delete(url) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError( - "User deletion failed. No such user. " - ) + return self.check_response(self.s.delete(url)) def grant_access(self, user, project): """Grants permission to to access resources of . """ @@ -57,29 +43,12 @@ def grant_access(self, user, project): self.project = project url = self.object_url('/auth/basic/user', self.user, 'add_project') payload = json.dumps({'project': self.project}) - q = self.s.post(url, data=payload) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError( - "Operation failed. Either user or project does not exist. " - ) - elif q.status_code == 409: - raise errors.DuplicateError( - "Access already granted. Duplicate operation. " - ) + return self.check_response(self.s.post(url, data=payload)) - def remove_access(self, user, project): + def revoke_access(self, user, project): """ Removes all access of to . """ self.user = user self.project = project url = self.object_url('/auth/basic/user', self.user, 'remove_project') payload = json.dumps({'project': self.project}) - q = self.s.post(url, data=payload) - if q.ok: - return - elif q.status_code == 404: - raise errors.NotFoundError( - "Operation failed. Either user; project or their " - "relationship does not exist. " - ) + return self.check_response(self.s.post(url, data=payload)) diff --git a/tests/unit/client.py b/tests/unit/client.py index 16180069..d70f0d93 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -23,6 +23,7 @@ import os import tempfile import subprocess +import time from subprocess import check_call, Popen from urlparse import urljoin import requests @@ -99,15 +100,15 @@ def make_config(): '[devel]', 'dry_run=True', '[auth]', - 'require_authentication = False', + 'require_authentication = True', '[headnode]', 'base_imgs = base-headnode, img1, img2, img3, img4', '[database]', 'uri = sqlite:///%s/haas.db' % tmpdir, '[extensions]', + 'haas.ext.auth.database =', 'haas.ext.switches.mock =', - 'haas.ext.auth.null =', 'haas.ext.switches.nexus =', 'haas.ext.switches.dell =', 'haas.ext.switches.brocade =', @@ -137,6 +138,7 @@ def cleanup((tmpdir, cwd)): def initialize_db(): """ Creates an database as defined in haas.cfg.""" check_call(['haas-admin', 'db', 'create']) + check_call(['haas', 'create_admin_user', username, password]) def run_server(cmd): @@ -155,20 +157,22 @@ def populate_server(): Once the server is started, this function will populate some mock objects to faciliate testing of the client library """ + sess = requests.Session() + sess.auth = (username, password) # Adding nodes, node-01 - node-06 url_node = 'http://127.0.0.1:8888/node/' api_nodename = 'http://schema.massopencloud.org/haas/v0/obm/' - for i in range(1, 9): + for i in range(1, 10): obminfo = { "type": api_nodename + 'ipmi', "host": "10.10.0.0"+repr(i), "user": "ipmi_u", "password": "pass1234" } - requests.put( + sess.put( url_node + 'node-0'+repr(i), data=json.dumps({"obm": obminfo}) ) - requests.put( + sess.put( url_node + 'node-0' + repr(i) + '/nic/eth0', data=json.dumps( {"macaddr": "aa:bb:cc:dd:ee:0" + repr(i)} ) @@ -176,7 +180,7 @@ def populate_server(): # Adding Projects proj-01 - proj-03 for i in ["proj-01", "proj-02", "proj-03"]: - requests.put('http://127.0.0.1:8888/project/' + i) + sess.put('http://127.0.0.1:8888/project/' + i) # Adding switches one for each driver url_switch = 'http://127.0.0.1:8888/switch/' @@ -200,48 +204,50 @@ def populate_server(): 'interface_type': 'TenGigabitEthernet' } - requests.put(url_switch + 'dell-01', data=json.dumps(dell_param)) - requests.put(url_switch + 'nexus-01', data=json.dumps(nexus_param)) - requests.put(url_switch + 'mock-01', data=json.dumps(mock_param)) - requests.put(url_switch + 'brocade-01', data=json.dumps(brocade_param)) + sess.put(url_switch + 'dell-01', data=json.dumps(dell_param)) + sess.put(url_switch + 'nexus-01', data=json.dumps(nexus_param)) + sess.put(url_switch + 'mock-01', data=json.dumps(mock_param)) + sess.put(url_switch + 'brocade-01', data=json.dumps(brocade_param)) # Adding ports to the mock switch, Connect nics to ports: for i in range(1, 8): - requests.put(url_switch + 'mock-01/port/gi1/0/' + repr(i)) - requests.post(url_switch + 'mock-01/port/gi1/0/' + repr(i) + '/' - 'connect_nic', data=json.dumps( - {'node': 'node-0' + repr(i), 'nic': 'eth0'} - )) + sess.put(url_switch + 'mock-01/port/gi1/0/' + repr(i)) + sess.post( + url_switch + 'mock-01/port/gi1/0/' + repr(i) + '/connect_nic', + data=json.dumps( + {'node': 'node-0' + repr(i), 'nic': 'eth0'} + ) + ) # Adding port gi1/0/8 to switch mock-01 without connecting it to any node. - requests.put(url_switch + 'mock-01/port/gi1/0/8') + sess.put(url_switch + 'mock-01/port/gi1/0/8') # Adding Projects proj-01 - proj-03 for i in ["proj-01", "proj-02", "proj-03"]: - requests.put('http://127.0.0.1:8888/project/' + i) + sess.put('http://127.0.0.1:8888/project/' + i) # Allocating nodes to projects url_project = 'http://127.0.0.1:8888/project/' # Adding nodes 1 to proj-01 - requests.post( + sess.post( url_project + 'proj-01' + '/connect_node', data=json.dumps({'node': 'node-01'}) ) # Adding nodes 2, 4 to proj-02 - requests.post( + sess.post( url_project + 'proj-02' + '/connect_node', data=json.dumps({'node': 'node-02'}) ) - requests.post( + sess.post( url_project + 'proj-02' + '/connect_node', data=json.dumps({'node': 'node-04'}) ) # Adding node 3, 5 to proj-03 - requests.post( + sess.post( url_project + 'proj-03' + '/connect_node', data=json.dumps({'node': 'node-03'}) ) - requests.post( + sess.post( url_project + 'proj-03' + '/connect_node', data=json.dumps({'node': 'node-05'}) ) @@ -249,7 +255,7 @@ def populate_server(): # Assigning networks to projects url_network = 'http://127.0.0.1:8888/network/' for i in ['net-01', 'net-02', 'net-03']: - requests.put( + sess.put( url_network + i, data=json.dumps( {"owner": "proj-01", "access": "proj-01", "net_id": ""} @@ -257,7 +263,7 @@ def populate_server(): ) for i in ['net-04', 'net-05']: - requests.put( + sess.put( url_network + i, data=json.dumps( {"owner": "proj-02", "access": "proj-02", "net_id": ""} @@ -266,13 +272,12 @@ def populate_server(): # -- SETUP -- -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture(scope="session", autouse=True) def create_setup(request): dir_names = make_config() initialize_db() proc1 = run_server(['haas', 'serve', '8888']) proc2 = run_server(['haas', 'serve_networks']) - import time time.sleep(1) populate_server() @@ -284,18 +289,18 @@ def fin(): @pytest.mark.usefixtures("create_setup") -class Test_Node: +class Test_node: """ Tests Node related client calls. """ def test_list_nodes_free(self): result = C.node.list('free') - assert result == [u'node-06', u'node-07', u'node-08'] + assert result == [u'node-06', u'node-07', u'node-08', u'node-09'] def test_list_nodes_all(self): result = C.node.list('all') assert result == [ u'node-01', u'node-02', u'node-03', u'node-04', u'node-05', - u'node-06', u'node-07', u'node-08' + u'node-06', u'node-07', u'node-08', u'node-09' ] def test_show_node(self): @@ -309,8 +314,8 @@ def test_show_node(self): u'networks': {}, u'label': u'eth0' } ], - u'name': u'node-07' - } + u'name': u'node-07' + } def test_power_cycle(self): result = C.node.power_cycle('node-07') @@ -328,11 +333,11 @@ def test_node_add_nic(self): def test_node_add_duplicate_nic(self): C.node.remove_nic('node-08', 'eth0') C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') - with pytest.raises(errors.DuplicateError): + with pytest.raises(Exception): C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') def test_nosuch_node_add_nic(self): - with pytest.raises(errors.NotFoundError): + with pytest.raises(Exception): C.node.add_nic('abcd', 'eth0', 'aa:bb:cc:dd:ee:ff') def test_remove_nic(self): @@ -341,7 +346,7 @@ def test_remove_nic(self): def test_remove_duplicate_nic(self): C.node.remove_nic('node-08', 'eth0') - with pytest.raises(errors.NotFoundError): + with pytest.raises(Exception): C.node.remove_nic('node-08', 'eth0') def test_node_connect_network(self): @@ -395,14 +400,14 @@ def test_project_create(self): def test_duplicate_project_create(self): """ test for catching duplicate name while creating new project. """ - C.project.create('dummy-01') + C.project.create('dummy-02') with pytest.raises(Exception): - C.project.create('dummy-01') + C.project.create('dummy-02') def test_project_delete(self): """ test for deleting project. """ - C.project.create('dummy-02') - result = C.project.delete('dummy-02') + C.project.create('dummy-03') + result = C.project.delete('dummy-03') assert result is None def test_error_project_delete(self): @@ -412,37 +417,37 @@ def test_error_project_delete(self): def test_project_connect_node(self): """ test for connecting node to project. """ - C.project.create('abcd') - result = C.project.connect('abcd', 'node-06') + C.project.create('proj-04') + result = C.project.connect('proj-04', 'node-06') assert result is None def test_project_connect_node_duplicate(self): """ test for erronous reconnecting node to project. """ - C.project.create('abcd') - C.project.connect('abcd', 'node-08') + C.project.create('proj-05') + C.project.connect('proj-05', 'node-07') with pytest.raises(Exception): - C.project.connect('abcd', 'node-08') + C.project.connect('proj-05', 'node-07') def test_project_connect_node_nosuchobject(self): """ test for connecting no such node or project """ - C.project.create('abcd') + C.project.create('proj-06') with pytest.raises(Exception): - C.project.connect('abcd', 'no-such-node') + C.project.connect('proj-06', 'no-such-node') with pytest.raises(Exception): C.project.connect('no-such-project', 'node-06') def test_project_detach_node(self): """ Test for correctly detaching node from project.""" - C.project.create('abcd') - C.project.connect('abcd', 'node-07') - result = C.project.detach('abcd', 'node-07') + C.project.create('proj-07') + C.project.connect('proj-07', 'node-08') + result = C.project.detach('proj-07', 'node-08') assert result is None def test_project_detach_node_nosuchobject(self): """ Test for while detaching node from project.""" - C.project.create('abcd') + C.project.create('proj-08') with pytest.raises(Exception): - C.project.detach('abcd', 'no-such-node') + C.project.detach('proj-08', 'no-such-node') with pytest.raises(Exception): C.project.detach('no-such-project', 'node-06') @@ -469,48 +474,46 @@ class Test_port: """ Tests port related client calls.""" def test_port_register(self): - result = C.port.register('dell-01', 'gi1/0/5') + result = C.port.register('mock-01', 'gi1/1/1') assert result is None def test_port_dupregister(self): - C.port.register('dell-01', 'gi1/0/6') - with pytest.raises(errors.DuplicateError): - C.port.register('dell-01', 'gi1/0/6') + C.port.register('mock-01', 'gi1/1/2') + with pytest.raises(Exception): + C.port.register('mock-01', 'gi1/1/2') def test_port_delete(self): - C.port.register('dell-01', 'gi1/0/5') - result = C.port.delete('dell-01', 'gi1/0/5') + C.port.register('mock-01', 'gi1/1/3') + result = C.port.delete('mock-01', 'gi1/1/3') assert result is None - def test_port_deleteerror(self): - C.port.register('dell-01', 'gi1/0/6') - C.port.delete('dell-01', 'gi1/0/6') - with pytest.raises(errors.NotFoundError): - C.port.delete('dell-01', 'gi1/0/6') + def test_port_delete_error(self): + C.port.register('mock-01', 'gi1/1/4') + C.port.delete('mock-01', 'gi1/1/4') + with pytest.raises(Exception): + C.port.delete('mock-01', 'gi1/1/4') def test_port_connect_nic(self): - C.port.register('dell-01', 'abcd') - result = C.port.connect_nic('dell-01', 'abcd', 'node-08', 'eth0') + C.port.register('mock-01', 'gi1/1/5') + result = C.port.connect_nic('mock-01', 'gi1/1/5', 'node-08', 'eth0') assert result is None def test_port_connect_nic_error(self): - C.port.register('dell-01', 'abcd') - C.port.connect_nic('dell-01', 'abcd', 'node-08', 'eth0') - with pytest.raises(errors.DuplicateError): - C.port.connect_nic('dell-01', 'abcd', 'node-08', 'eth0') + C.port.register('mock-01', 'gi1/1/6') + C.port.connect_nic('mock-01', 'gi1/1/6', 'node-09', 'eth0') + with pytest.raises(Exception): + C.port.connect_nic('mock-01', 'gi1/1/6', 'node-08', 'eth0') def test_port_detach_nic(self): - C.port.register('dell-01', 'gi1/0/11') - C.port.connect_nic('dell-01', 'gi1/0/11', 'node-08', 'eth0') - result = C.port.detach_nic('dell-01', 'gi1/0/11') + C.port.register('mock-01', 'gi1/1/7') + C.port.connect_nic('mock-01', 'gi1/1/7', 'node-09', 'eth0') + result = C.port.detach_nic('mock-01', 'gi1/1/7') assert result is None def test_port_detach_nic_error(self): - C.port.register('dell-01', 'gi1/0/11') - C.port.connect_nic('dell-01', 'gi1/0/11', 'node-08', 'eth0') - C.port.detach_nic('dell-01', 'gi1/0/11') - with pytest.raises(errors.NotFoundError): - C.port.detach_nic('dell-01', 'gi1/0/11') + C.port.register('mock-01', 'gi1/1/8') + with pytest.raises(Exception): + C.port.detach_nic('mock-01', 'gi1/1/8') @pytest.mark.usefixtures("create_setup") @@ -519,9 +522,144 @@ class Test_user: def test_user_create(self): """ Test user creation. """ - result1 = C.user.create('bill', 'pass1234', 'regular') - result2 = C.user.create('bob', 'pass1234', 'regular') + result1 = C.user.create('billy', 'pass1234', 'admin') + result2 = C.user.create('bobby', 'pass1234', 'regular') + assert result1 is None + assert result2 is None + + def test_user_create_duplicate(self): + """ Test duplicate user creation. """ + C.user.create('bill', 'pass1234', 'regular') + with pytest.raises(Exception): + C.user.create('bill', 'pass1234', 'regular') + + def test_user_delete(self): + """ Test user deletion. """ + C.user.create('jack', 'pass1234', 'admin') + result = C.user.delete('jack') + assert result is None + + def test_user_delete_error(self): + """ Test error condition in user deletion. """ + C.user.create('Ata', 'pass1234', 'admin') + C.user.delete('Ata') + with pytest.raises(Exception): + C.user.delete('Ata') + + def test_user_grant_access(self): + """ test granting user access to project. """ + C.project.create('proj-sample') + C.user.create('Sam', 'pass1234', 'regular') + result = C.user.grant_access('Sam', 'proj-sample') + assert result is None + + def test_user_grant_access_error(self): + """Test error condition while granting user access to a project.""" + C.project.create('test-proj01') + C.user.create('sam01', 'pass1234', 'regular') + C.user.grant_access('sam01', 'test-proj01') + with pytest.raises(Exception): + C.user.grant_access('sam01', 'test-proj01') + + def test_user_revoke_access(self): + """Test revoking user's access to a project. """ + C.project.create('test-proj02') + C.user.create('sam02', 'pass1234', 'regular') + C.user.grant_access('sam02', 'test-proj02') + result = C.user.revoke_access('sam02', 'test-proj02') + assert result is None + + def test_user_revoke_access_error(self): + """Test error condition while revoking user access to a project. """ + C.project.create('test-proj03') + C.user.create('sam03', 'pass1234', 'regular') + C.user.create('xxxx', 'pass1234', 'regular') + C.user.grant_access('sam03', 'test-proj03') + C.user.revoke_access('sam03', 'test-proj03') + with pytest.raises(Exception): + C.user.revoke_access('sam03', 'test_proj03') + + +@pytest.mark.usefixtures("create_setup") +class Test_network: + """ Tests network related client calls. """ + + def test_network_list(self): + """ Test list of networks. """ + result = C.network.list() + assert result == { + u'net-01': {u'network_id': u'1001', u'projects': [u'proj-01']}, + u'net-02': {u'network_id': u'1002', u'projects': [u'proj-01']}, + u'net-03': {u'network_id': u'1003', u'projects': [u'proj-01']}, + u'net-04': {u'network_id': u'1004', u'projects': [u'proj-02']}, + u'net-05': {u'network_id': u'1005', u'projects': [u'proj-02']} + } + + def test_network_show(self): + """ Test show network. """ + result = C.network.show('net-01') + assert result == { + u'access': [u'proj-01'], + u'channels': [u'vlan/native', u'vlan/1001'], + u'name': u'net-01', + u'owner': u'proj-01' + } + + def test_network_create(self): + """ Test create network. """ + result = C.network.create('net-abcd', 'proj-01', 'proj-01', '') + assert result is None + + def test_network_create_duplicate(self): + """ Test error condition in create network. """ + C.network.create('net-123', 'proj-01', 'proj-01', '') + with pytest.raises(Exception): + C.network.create('net-123', 'proj-01', 'proj-01', '') + + def test_network_delete(self): + """ Test network deletion """ + C.network.create('net-xyz', 'proj-01', 'proj-01', '') + result = C.network.delete('net-xyz') + assert result is None + + def test_network_delete_duplicate(self): + """ Test error condition in delete network. """ + C.network.create('net-xyz', 'proj-01', 'proj-01', '') + C.network.delete('net-xyz') + with pytest.raises(Exception): + C.network.delete('net-xyz') + + def test_network_grant_project_access(self): + """ Test granting a project access to a network. """ + C.network.create('newnet01', 'admin', '', '') + result1 = C.network.grant_access('proj-02', 'newnet01') + result2 = C.network.grant_access('proj-03', 'newnet01') assert result1 is None assert result2 is None + def test_network_grant_project_access_error(self): + """ Test error while granting a project access to a network. """ + C.network.create('newnet04', 'admin', '', '') + C.network.grant_access('proj-02', 'newnet04') + with pytest.raises(Exception): + C.network.grant_access('proj-02', 'newnet04') + + def test_network_revoke_project_access(self): + """ Test revoking a project's access to a network. """ + C.network.create('newnet02', 'admin', '', '') + C.network.grant_access('proj-02', 'newnet02') + result = C.network.revoke_access('proj-02', 'newnet02') + assert result is None + + def test_network_revoke_project_access_error(self): + """ + Test error condition when revoking project's access to a network. + """ + C.network.create('newnet03', 'admin', '', '') + C.network.grant_access('proj-02', 'newnet03') + C.network.revoke_access('proj-02', 'newnet03') + with pytest.raises(Exception): + C.network.revoke_access('proj-02', 'newnet03') + + # End of tests ## From d266f4d29b71da84195faaf0fabb3fe0703a7e73 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Thu, 23 Feb 2017 10:38:47 -0500 Subject: [PATCH 51/71] Minor refactoring. moving to only for methods. --- haas/client/base.py | 7 +++--- haas/client/network.py | 31 ++++++------------------- haas/client/node.py | 52 +++++++++++------------------------------- haas/client/project.py | 44 ++++++++++++++--------------------- haas/client/switch.py | 22 +++--------------- haas/client/user.py | 22 ++++++------------ 6 files changed, 50 insertions(+), 128 deletions(-) diff --git a/haas/client/base.py b/haas/client/base.py index f3f5f752..6a78b02d 100644 --- a/haas/client/base.py +++ b/haas/client/base.py @@ -40,12 +40,11 @@ def object_url(self, *args): return url def check_response(self, response): - self.res = response - if self.res.ok: + if response.ok: if response.request.method == 'GET': - return self.res.json() + return response.json() else: # For methods PUT, POST, DELETE return else: - e = self.res.json() + e = response.json() raise errors.FailedAPICallException(e['msg']) diff --git a/haas/client/network.py b/haas/client/network.py index b12ae8b8..45fff54c 100644 --- a/haas/client/network.py +++ b/haas/client/network.py @@ -14,52 +14,35 @@ def list(self): def show(self, network): """ Shows attributes of a network. """ - self.network = network - url = self.object_url('network', self.network) + url = self.object_url('network', network) return self.check_response(self.s.get(url)) def create(self, network, owner, access, net_id): """ Create a link-layer . See docs/networks.md for details. """ - - self.network = network - self.owner = owner - self.access = access - self.net_id = net_id - - url = self.object_url('network', self.network) + url = self.object_url('network', network) payload = json.dumps({ - 'owner': self.owner, 'access': self.access, - 'net_id': self.net_id + 'owner': owner, 'access': access, + 'net_id': net_id }) return self.check_response(self.s.put(url, data=payload)) def delete(self, network): """ Delete a . """ - - self.network = network - url = self.object_url('network', self.network) + url = self.object_url('network', network) return self.check_response(self.s.delete(url)) def grant_access(self, project, network): """ Grants access to . """ - - self.project = project - self.network = network - url = self.object_url( - 'network', self.network, 'access', self.project + 'network', network, 'access', project ) return self.check_response(self.s.put(url)) def revoke_access(self, project, network): """ Removes access of from . """ - - self.project = project - self.network = network - url = self.object_url( - 'network', self.network, 'access', self.project + 'network', network, 'access', project ) return self.check_response(self.s.delete(url)) diff --git a/haas/client/node.py b/haas/client/node.py index 30f56381..30083f5a 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -10,16 +10,13 @@ class Node(ClientBase): def list(self, is_free): """ List all nodes that HIL manages """ - self.is_free = is_free - url = self.object_url('nodes', self.is_free) + url = self.object_url('nodes', is_free) return self.check_response(self.s.get(url)) def show(self, node_name): """ Shows attributes of a given node """ - self.node_name = node_name - url = self.object_url('node', self.node_name) - q = self.s.get(url) + url = self.object_url('node', node_name) return self.check_response(self.s.get(url)) def register(self, node, subtype, *args): @@ -30,8 +27,6 @@ def register(self, node, subtype, *args): # Node requires which OBM, and knows arguments required # for successful node registration. - self.node = node - self.subtype = subtype obm_api = "http://schema.massopencloud.org/haas/v0/obm/" obm_types = ["ipmi", "mock"] # FIXME: In future obm_types should be dynamically fetched @@ -41,65 +36,46 @@ def register(self, node, subtype, *args): def delete(self, node_name): """ Deletes the node from database. """ - self.node_name = node_name - url = self.object_url('node', self.node_name) + url = self.object_url('node', node_name) return check_response(self.s.delete(url)) def power_cycle(self, node_name): """ Power cycles the """ - self.node_name = node_name url = self.object_url('node', node_name, 'power_cycle') return self.check_response(self.s.post(url)) def power_off(self, node_name): """ Power offs the """ - self.node_name = node_name - url = self.object_url('node', self.node_name, 'power_off') + url = self.object_url('node', node_name, 'power_off') return self.check_response(self.s.post(url)) def add_nic(self, node_name, nic_name, macaddr): """ adds a to """ - self.node_name = node_name - self.nic_name = nic_name - self.macaddr = macaddr - url = self.object_url('node', self.node_name, 'nic', self.nic_name) - payload = json.dumps({'macaddr': self.macaddr}) + url = self.object_url('node', node_name, 'nic', nic_name) + payload = json.dumps({'macaddr': macaddr}) return self.check_response(self.s.put(url, data=payload)) def remove_nic(self, node_name, nic_name): """ remove a from """ - self.node_name = node_name - self.nic_name = nic_name - url = self.object_url('node', self.node_name, 'nic', self.nic_name) + url = self.object_url('node', node_name, 'nic', nic_name) return self.check_response(self.s.delete(url)) def connect_network(self, node, nic, network, channel): """ Connect to on given and """ - - self.node = node - self.nic = nic - self.network = network - self.channel = channel - url = self.object_url( - 'node', self.node, 'nic', self.nic, 'connect_network' + 'node', node, 'nic', nic, 'connect_network' ) payload = json.dumps({ - 'network': self.network, 'channel': self.channel + 'network': network, 'channel': channel }) return self.check_response(self.s.post(url, payload)) def detach_network(self, node, nic, network): """ Disconnect from on the given . """ - - self.node = node - self.nic = nic - self.network = network - url = self.object_url( - 'node', self.node, 'nic', self.nic, 'detach_network' + 'node', node, 'nic', nic, 'detach_network' ) - payload = json.dumps({'network': self.network}) + payload = json.dumps({'network': network}) return self.check_response(self.s.post(url, payload)) def show_console(self, node): @@ -108,12 +84,10 @@ def show_console(self, node): def start_console(self, node): """ Start logging console output from """ - self.node = node - url = self.object_url('node', self.node, 'console') + url = self.object_url('node', node, 'console') return self.check_response(self.s.put(url)) def stop_console(self, node): """ Stop logging console output from and delete the log""" - self.node = node - url = self.object_url('node', self.node, 'console') + url = self.object_url('node', node, 'console') return self.check_response(self.s.delete(url)) diff --git a/haas/client/project.py b/haas/client/project.py index 01c89aab..c3606626 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -10,55 +10,45 @@ class Project(ClientBase): def list(self): """ Lists all projects under HIL """ - self.url = self.object_url('/projects') - return self.check_response(self.s.get(self.url)) + url = self.object_url('/projects') + return self.check_response(self.s.get(url)) def nodes_in(self, project_name): """ Lists nodes allocated to project """ - self.project_name = project_name - self.url = self.object_url('project', self.project_name, 'nodes') - return self.check_response(self.s.get(self.url)) + url = self.object_url('project', project_name, 'nodes') + return self.check_response(self.s.get(url)) def networks_in(self, project_name): """ Lists nodes allocated to project """ - self.project_name = project_name - self.url = self.object_url( - 'project', self.project_name, 'networks' + url = self.object_url( + 'project', project_name, 'networks' ) - return self.check_response(self.s.get(self.url)) + return self.check_response(self.s.get(url)) def create(self, project_name): """ Creates a project named """ - self.project_name = project_name - self.url = self.object_url('project', self.project_name) - return self.check_response(self.s.put(self.url)) + url = self.object_url('project', project_name) + return self.check_response(self.s.put(url)) def delete(self, project_name): """ Deletes a project named """ - self.project_name = project_name - self.url = self.object_url('project', self.project_name) - return self.check_response(self.s.delete(self.url)) + url = self.object_url('project', project_name) + return self.check_response(self.s.delete(url)) def connect(self, project_name, node_name): """ Adds a node to a project. """ - self.project_name = project_name - self.node_name = node_name - - self.url = self.object_url( - 'project', self.project_name, 'connect_node' + url = self.object_url( + 'project', project_name, 'connect_node' ) - self.payload = json.dumps({'node': self.node_name}) + self.payload = json.dumps({'node': node_name}) return self.check_response( - self.s.post(self.url, data=self.payload) + self.s.post(url, data=self.payload) ) def detach(self, project_name, node_name): """ Adds a node to a project. """ - self.project_name = project_name - self.node_name = node_name - - self.url = self.object_url('project', project_name, 'detach_node') + url = self.object_url('project', project_name, 'detach_node') self.payload = json.dumps({'node': node_name}) return self.check_response( - self.s.post(self.url, data=self.payload) + self.s.post(url, data=self.payload) ) diff --git a/haas/client/switch.py b/haas/client/switch.py index e1e7809b..d67eabf5 100644 --- a/haas/client/switch.py +++ b/haas/client/switch.py @@ -24,15 +24,12 @@ def register(self, switch, subtype, *args): pass def delete(self, switch): - self.switch = switch - url = self.object_url('switch', self.switch) + url = self.object_url('switch', switch) return self.check_response(self.s.delete(url)) def show(self, switch): """ Shows attributes of . """ - - self.switch = switch - url = self.object_url('switch', self.switch) + url = self.object_url('switch', switch) return self.check_response(self.s.get(url)) @@ -41,34 +38,21 @@ class Port(ClientBase): def register(self, switch, port): """Register a with . """ - self.switch = switch - self.port = port - url = self.object_url('switch', switch, 'port', port) return self.check_response(self.s.put(url)) def delete(self, switch, port): """ Deletes information of the for """ - self.switch = switch - self.port = port - url = self.object_url('switch', switch, 'port', port) return self.check_response(self.s.delete(url)) def connect_nic(self, switch, port, node, nic): """ Connects of to of . """ - self.switch = switch - self.port = port - self.node = node - self.nic = nic - url = self.object_url('switch', switch, 'port', port, 'connect_nic') - payload = json.dumps({'node': self.node, 'nic': self.nic}) + payload = json.dumps({'node': node, 'nic': nic}) return self.check_response(self.s.post(url, payload)) def detach_nic(self, switch, port): """" Detaches of . """ - self.switch = switch - self.port = port url = self.object_url('switch', switch, 'port', port, 'detach_nic') return self.check_response(self.s.post(url)) diff --git a/haas/client/user.py b/haas/client/user.py index 6e287f69..898f04b0 100644 --- a/haas/client/user.py +++ b/haas/client/user.py @@ -13,24 +13,20 @@ def create(self, username, password, privilege): may by either "admin" or "regular", and determines whether a user is authorized for administrative privileges """ - self.username = username - self.password = password - self.privilege = privilege - url = self.object_url('/auth/basic/user', self.username) + url = self.object_url('/auth/basic/user', username) if privilege not in('admin', 'regular'): raise TypeError( "invalid privilege type: must be either 'admin' or 'regular'." ) payload = json.dumps({ - 'password': self.password, 'is_admin': privilege == 'admin', + 'password': password, 'is_admin': privilege == 'admin', }) return self.check_response(self.s.put(url, data=payload)) def delete(self, username): """Deletes the user . """ - self.username = username - url = self.object_url('/auth/basic/user', self.username) + url = self.object_url('/auth/basic/user', username) return self.check_response(self.s.delete(url)) def grant_access(self, user, project): @@ -39,16 +35,12 @@ def grant_access(self, user, project): # access to projects # like "grant_read_access", "grant_write_access", # "grant_all_access", etc - self.user = user - self.project = project - url = self.object_url('/auth/basic/user', self.user, 'add_project') - payload = json.dumps({'project': self.project}) + url = self.object_url('/auth/basic/user', user, 'add_project') + payload = json.dumps({'project': project}) return self.check_response(self.s.post(url, data=payload)) def revoke_access(self, user, project): """ Removes all access of to . """ - self.user = user - self.project = project - url = self.object_url('/auth/basic/user', self.user, 'remove_project') - payload = json.dumps({'project': self.project}) + url = self.object_url('/auth/basic/user', user, 'remove_project') + payload = json.dumps({'project': project}) return self.check_response(self.s.post(url, data=payload)) From a8a0a7112094ffb0494f32113020db7dc6058140 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 27 Feb 2017 11:40:53 -0500 Subject: [PATCH 52/71] Fixes tests --- haas/cli.py | 6 ++++-- tests/unit/client.py | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 2bd48981..139f7ccb 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -542,7 +542,7 @@ def headnode_delete_hnic(headnode, nic): def node_connect_network(node, nic, network, channel): """Connect to on given and """ check_client_lib( - lambda: C.node.connect_network(node, nic, network,channel) + lambda: C.node.connect_network(node, nic, network, channel) ) @@ -673,7 +673,9 @@ def port_delete(switch, port): @cmd def port_connect_nic(switch, port, node, nic): """Connect a on a to a on a """ - check_clientlib_response(lambda: C.port.connect_nic(switch, port, node, nic)) + check_clientlib_response( + lambda: C.port.connect_nic(switch, port, node, nic) + ) @cmd diff --git a/tests/unit/client.py b/tests/unit/client.py index d70f0d93..a6a33759 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -272,7 +272,7 @@ def populate_server(): # -- SETUP -- -@pytest.fixture(scope="session", autouse=True) +@pytest.fixture(scope="module") def create_setup(request): dir_names = make_config() initialize_db() @@ -280,11 +280,13 @@ def create_setup(request): proc2 = run_server(['haas', 'serve_networks']) time.sleep(1) populate_server() + print("coming from create_setup") def fin(): proc1.terminate() proc2.terminate() cleanup(dir_names) + print("tearing down HIL setup") request.addfinalizer(fin) From df915fdd5ddb5c3cbe0b5bdaa68b26a4179e571a Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 28 Feb 2017 12:21:00 -0500 Subject: [PATCH 53/71] Updates setup.py to install dependencies to support keystone. Minor fixes to cli. --- haas/cli.py | 7 ------- setup.py | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 139f7ccb..1dc6c60a 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -234,13 +234,6 @@ def check_clientlib_response(fun): sys.stderr.write('Error: %s\n' % e.message) -def clientlib_response(fun, *fun_args): - try: - return fun(*fun_args) - except Exception as e: - sys.stderr.write('Error: %s\n' % e.message) - - def check_status_code(response): if response.status_code < 200 or response.status_code >= 300: sys.stderr.write('Unexpected status code: %d\n' % response.status_code) diff --git a/setup.py b/setup.py index 6301cf85..26b8988b 100644 --- a/setup.py +++ b/setup.py @@ -96,4 +96,5 @@ def _get_readme(): 'pep8>=1.7.0', 'requests_mock>=1.0.0,<2.0', 'lxml>=3.6.0,<4.0', + 'keystoneauth1>=2.16,<3.0', ]) From e05786fb82daca0eb2c7f50dfc1d268f9064b121 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 28 Feb 2017 13:24:18 -0500 Subject: [PATCH 54/71] Makes changes suggested by @zenhack. --- haas/cli.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 1dc6c60a..31cf1a29 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -699,11 +699,11 @@ def list_nodes(is_free): """ q = check_clientlib_response(lambda: C.node.list(is_free)) if is_free == 'all': - sys.stdout.write('All nodes {}\t: {}\n'.format(len(q), " ".join(q))) + sys.stdout.write('All nodes %s\t: %s\n' % (len(q), " ".join(q))) elif is_free == 'free': - sys.stdout.write('Free nodes {}\t: {}\n'.format(len(q), " ".join(q))) + sys.stdout.write('Free nodes %s\t: %s\n' % (len(q), " ".join(q))) else: - sys.stdout.write('Error: {} is an invalid argument\n'.format(is_free)) + sys.stdout.write('Error: %s is an invalid argument\n' % (is_free)) @cmd @@ -718,7 +718,7 @@ def list_project_networks(project): """List all networks attached to a """ q = check_clientlib_response(lambda: C.project.networks_in(project)) sys.stdout.write( - "Networks allocated to {}\t: {}\n".format(project, " ".join(q)) + "Networks allocated to %s\t: %s\n" % (project, " ".join(q)) ) @@ -727,7 +727,7 @@ def show_switch(switch): """Display information about """ q = check_clientlib_response(lambda: C.switch.show(switch)) for item in q.items(): - sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) + sys.stdout.write("%s\t : %s\n" % (item[0], item[1])) @cmd @@ -735,7 +735,7 @@ def list_networks(): """List all networks""" q = check_clientlib_response(lambda: C.network.list()) for item in q.items(): - sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) + sys.stdout.write('%s \t : %s\n' % (item[0], item[1])) @cmd @@ -743,7 +743,7 @@ def show_network(network): """Display information about """ q = check_clientlib_response(lambda: C.network.show(network)) for item in q.items(): - sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) + sys.stdout.write("%s\t : %s\n" % (item[0], item[1])) @cmd @@ -753,7 +753,7 @@ def show_node(node): """ q = check_clientlib_response(lambda: C.node.show(node)) for item in q.items(): - sys.stdout.write("{}\t : {}\n".format(item[0], item[1])) + sys.stdout.write("%s\t : %s\n" % (item[0], item[1])) @cmd From 3b1ece8ff048559c84060bb4e0cdcbe19a50574c Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 28 Feb 2017 20:18:41 -0500 Subject: [PATCH 55/71] Fixes doc string and other minor changes --- haas/cli.py | 4 ++-- haas/client/auth.py | 6 ++++-- haas/client/base.py | 14 ++++++++------ haas/client/network.py | 21 ++++++++++++--------- haas/client/node.py | 33 +++++++++++++++++---------------- haas/client/project.py | 20 +++++++++++--------- haas/client/switch.py | 23 ++++++++++++----------- haas/client/user.py | 27 +++++++++++++-------------- 8 files changed, 79 insertions(+), 69 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 31cf1a29..f7ec2420 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -380,13 +380,13 @@ def list_projects(): @cmd def user_add_project(user, project): """Add to """ - check_clientlib_response(lambda: C.user.grant_access(user, project)) + check_clientlib_response(lambda: C.user.add(user, project)) @cmd def user_remove_project(user, project): """Remove from """ - check_clientlib_response(lambda: C.user.remove_access(user, project)) + check_clientlib_response(lambda: C.user.remove(user, project)) @cmd diff --git a/haas/client/auth.py b/haas/client/auth.py index c54a2749..50fc55da 100644 --- a/haas/client/auth.py +++ b/haas/client/auth.py @@ -5,7 +5,8 @@ def db_auth(username, password): - """ For database as authentication backend, this function prepares + """For database as authentication backend, this function prepares + the default client session. """ s = requests.Session() @@ -17,7 +18,8 @@ def keystone_auth( os_auth_url, os_username, os_password, os_user_domain_id, os_project_name, os_project_domain_id ): - """ This functions setup requisites for the http session when using + """This functions setup requisites for the http session when using + keystone as the aunthentication backend. """ auth = v3.Password( diff --git a/haas/client/base.py b/haas/client/base.py index 6a78b02d..cf7897ea 100644 --- a/haas/client/base.py +++ b/haas/client/base.py @@ -8,7 +8,8 @@ class ClientBase(object): - """ Main class which contains all the methods to + """Main class which contains all the methods to + -- ensure input complies to API requisites -- generates correct format for server API on behalf of the client -- parses output from received from the server. @@ -18,11 +19,12 @@ class ClientBase(object): def __init__(self, endpoint=None, sess=None): """ Initialize an instance of the library with following parameters. - endpoint: stands for the http endpoint eg. endpoint=http://127.0.0.1 - sess: depending on the authentication backend (db vs keystone) the - parameters required to make up the session vary. - user: username as which you wish to connect to HaaS - Currently all this information is fetched from the user's environment. + + endpoint: stands for the http endpoint eg. endpoint=http://127.0.0.1 + sess: depending on the authentication backend (db vs keystone) the + parameters required to make up the session vary. + user: username as which you wish to connect to HaaS + Currently all this information is fetched from the user's environment. """ self.endpoint = endpoint self.s = sess diff --git a/haas/client/network.py b/haas/client/network.py index 45fff54c..4d00d13f 100644 --- a/haas/client/network.py +++ b/haas/client/network.py @@ -4,22 +4,25 @@ class Network(ClientBase): - """ Consists of calls to query and manipulate network related - objects and relations """ + """Consists of calls to query and manipulate network related + + objects and relations. + """ def list(self): - """ Lists all projects under HIL """ + """Lists all projects under HIL """ url = self.object_url('networks') return self.check_response(self.s.get(url)) def show(self, network): - """ Shows attributes of a network. """ + """Shows attributes of a network. """ url = self.object_url('network', network) return self.check_response(self.s.get(url)) def create(self, network, owner, access, net_id): - """ Create a link-layer . See docs/networks.md for - details. + """Create a link-layer . + + See docs/networks.md for details. """ url = self.object_url('network', network) payload = json.dumps({ @@ -29,19 +32,19 @@ def create(self, network, owner, access, net_id): return self.check_response(self.s.put(url, data=payload)) def delete(self, network): - """ Delete a . """ + """Delete a . """ url = self.object_url('network', network) return self.check_response(self.s.delete(url)) def grant_access(self, project, network): - """ Grants access to . """ + """Grants access to . """ url = self.object_url( 'network', network, 'access', project ) return self.check_response(self.s.put(url)) def revoke_access(self, project, network): - """ Removes access of from . """ + """Removes access of from . """ url = self.object_url( 'network', network, 'access', project ) diff --git a/haas/client/node.py b/haas/client/node.py index 30083f5a..a92895de 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -4,23 +4,24 @@ class Node(ClientBase): - """ Consists of calls to query and manipulate node related + """Consists of calls to query and manipulate node related + objects and relations. """ def list(self, is_free): - """ List all nodes that HIL manages """ + """List all nodes that HIL manages """ url = self.object_url('nodes', is_free) return self.check_response(self.s.get(url)) def show(self, node_name): - """ Shows attributes of a given node """ + """Shows attributes of a given node """ url = self.object_url('node', node_name) return self.check_response(self.s.get(url)) def register(self, node, subtype, *args): - """ Register a node with appropriate OBM driver. """ + """Register a node with appropriate OBM driver. """ # Registering a node requires apriori knowledge of the # available OBM driver and its corresponding arguments. # We assume that the HIL administrator is aware as to which @@ -32,36 +33,36 @@ def register(self, node, subtype, *args): # FIXME: In future obm_types should be dynamically fetched # from haas.cfg, need a new api call for querying available # and currently active drivers for HIL - pass +# Not implemented yet. def delete(self, node_name): - """ Deletes the node from database. """ + """Deletes the node from database. """ url = self.object_url('node', node_name) return check_response(self.s.delete(url)) def power_cycle(self, node_name): - """ Power cycles the """ + """Power cycles the """ url = self.object_url('node', node_name, 'power_cycle') return self.check_response(self.s.post(url)) def power_off(self, node_name): - """ Power offs the """ + """Power offs the """ url = self.object_url('node', node_name, 'power_off') return self.check_response(self.s.post(url)) def add_nic(self, node_name, nic_name, macaddr): - """ adds a to """ + """Add a to """ url = self.object_url('node', node_name, 'nic', nic_name) payload = json.dumps({'macaddr': macaddr}) return self.check_response(self.s.put(url, data=payload)) def remove_nic(self, node_name, nic_name): - """ remove a from """ + """Remove a from """ url = self.object_url('node', node_name, 'nic', nic_name) return self.check_response(self.s.delete(url)) def connect_network(self, node, nic, network, channel): - """ Connect to on given and """ + """Connect to on given and """ url = self.object_url( 'node', node, 'nic', nic, 'connect_network' ) @@ -71,7 +72,7 @@ def connect_network(self, node, nic, network, channel): return self.check_response(self.s.post(url, payload)) def detach_network(self, node, nic, network): - """ Disconnect from on the given . """ + """Disconnect from on the given . """ url = self.object_url( 'node', node, 'nic', nic, 'detach_network' ) @@ -79,15 +80,15 @@ def detach_network(self, node, nic, network): return self.check_response(self.s.post(url, payload)) def show_console(self, node): - """ Display console log for """ - pass + """Display console log for """ + # Not implemented yet. def start_console(self, node): - """ Start logging console output from """ + """Start logging console output from """ url = self.object_url('node', node, 'console') return self.check_response(self.s.put(url)) def stop_console(self, node): - """ Stop logging console output from and delete the log""" + """Stop logging console output from and delete the log""" url = self.object_url('node', node, 'console') return self.check_response(self.s.delete(url)) diff --git a/haas/client/project.py b/haas/client/project.py index c3606626..6a6ade76 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -4,39 +4,41 @@ class Project(ClientBase): - """ Consists of calls to query and manipulate project related - objects and relations """ + """Consists of calls to query and manipulate project related + + objects and relations. + """ def list(self): - """ Lists all projects under HIL """ + """Lists all projects under HIL """ url = self.object_url('/projects') return self.check_response(self.s.get(url)) def nodes_in(self, project_name): - """ Lists nodes allocated to project """ + """Lists nodes allocated to project """ url = self.object_url('project', project_name, 'nodes') return self.check_response(self.s.get(url)) def networks_in(self, project_name): - """ Lists nodes allocated to project """ + """Lists nodes allocated to project """ url = self.object_url( 'project', project_name, 'networks' ) return self.check_response(self.s.get(url)) def create(self, project_name): - """ Creates a project named """ + """Creates a project named """ url = self.object_url('project', project_name) return self.check_response(self.s.put(url)) def delete(self, project_name): - """ Deletes a project named """ + """Deletes a project named """ url = self.object_url('project', project_name) return self.check_response(self.s.delete(url)) def connect(self, project_name, node_name): - """ Adds a node to a project. """ + """Adds a node to a project. """ url = self.object_url( 'project', project_name, 'connect_node' ) @@ -46,7 +48,7 @@ def connect(self, project_name, node_name): ) def detach(self, project_name, node_name): - """ Adds a node to a project. """ + """Adds a node to a project. """ url = self.object_url('project', project_name, 'detach_node') self.payload = json.dumps({'node': node_name}) return self.check_response( diff --git a/haas/client/switch.py b/haas/client/switch.py index d67eabf5..80b48353 100644 --- a/haas/client/switch.py +++ b/haas/client/switch.py @@ -4,37 +4,38 @@ class Switch(ClientBase): - """ Consists of calls to query and manipulate node related - objects and relations """ + """Consists of calls to query and manipulate node related + + objects and relations. + """ def list(self): - """ List all nodes that HIL manages """ + """List all nodes that HIL manages """ url = self.object_url('/switches') return self.check_response(self.s.get(url)) def register(self, switch, subtype, *args): - """ Registers a switch with name and + """Registers a switch with name and model , and relevant arguments in <*args> """ # It is assumed that the HIL administrator is aware of # of the switches HIL will control and has read the # HIL documentation to use appropriate flags to register # it with HIL. - - pass +# Not Implemented yet. def delete(self, switch): url = self.object_url('switch', switch) return self.check_response(self.s.delete(url)) def show(self, switch): - """ Shows attributes of . """ + """Shows attributes of . """ url = self.object_url('switch', switch) return self.check_response(self.s.get(url)) class Port(ClientBase): - """ Port related operations. """ + """Port related operations. """ def register(self, switch, port): """Register a with . """ @@ -42,17 +43,17 @@ def register(self, switch, port): return self.check_response(self.s.put(url)) def delete(self, switch, port): - """ Deletes information of the for """ + """Deletes information of the for """ url = self.object_url('switch', switch, 'port', port) return self.check_response(self.s.delete(url)) def connect_nic(self, switch, port, node, nic): - """ Connects of to of . """ + """Connects of to of . """ url = self.object_url('switch', switch, 'port', port, 'connect_nic') payload = json.dumps({'node': node, 'nic': nic}) return self.check_response(self.s.post(url, payload)) def detach_nic(self, switch, port): - """" Detaches of . """ + """"Detaches of . """ url = self.object_url('switch', switch, 'port', port, 'detach_nic') return self.check_response(self.s.post(url)) diff --git a/haas/client/user.py b/haas/client/user.py index 898f04b0..512bda63 100644 --- a/haas/client/user.py +++ b/haas/client/user.py @@ -4,19 +4,22 @@ class User(ClientBase): - """ Consists of calls to query and manipulate users related - objects and relations. + """Consists of calls to query and + + manipulate users related objects and relations. """ def create(self, username, password, privilege): """Create a user with password . - may by either "admin" or "regular", and determines whether a - user is authorized for administrative privileges - """ + + may by either "admin" or "regular", + and determines whether a user is authorized for + administrative privileges. + """ url = self.object_url('/auth/basic/user', username) if privilege not in('admin', 'regular'): - raise TypeError( + raise ValueError( "invalid privilege type: must be either 'admin' or 'regular'." ) payload = json.dumps({ @@ -29,18 +32,14 @@ def delete(self, username): url = self.object_url('/auth/basic/user', username) return self.check_response(self.s.delete(url)) - def grant_access(self, user, project): - """Grants permission to to access resources of . """ - # Note: Named such so that in future we can have granular - # access to projects - # like "grant_read_access", "grant_write_access", - # "grant_all_access", etc + def add(self, user, project): + """Adds to a . """ url = self.object_url('/auth/basic/user', user, 'add_project') payload = json.dumps({'project': project}) return self.check_response(self.s.post(url, data=payload)) - def revoke_access(self, user, project): - """ Removes all access of to . """ + def remove(self, user, project): + """Removes all access of to . """ url = self.object_url('/auth/basic/user', user, 'remove_project') payload = json.dumps({'project': project}) return self.check_response(self.s.post(url, data=payload)) From 6a8b93274c2a452d622634191b4948dece45a538 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 20 Mar 2017 02:03:56 -0400 Subject: [PATCH 56/71] Changes client library to accept a complete object for an HTTP client unlike only session object as was previously the case. Changes to all client calls to make it more general than before. Raises error for calls not implemented yet. Removes the auth.py and errors.py readujsted under base.py Removes keystone dependency from setup.py Makes changes to cli.py and unit tests to maintain integrity. --- haas/cli.py | 113 ++++++----------------------------------- haas/client/auth.py | 34 ------------- haas/client/base.py | 17 +++---- haas/client/client.py | 105 ++++++++++++++++++++++++++++++++++---- haas/client/errors.py | 22 -------- haas/client/network.py | 15 +++--- haas/client/node.py | 33 +++++++----- haas/client/project.py | 15 +++--- haas/client/switch.py | 19 +++---- haas/client/user.py | 17 +++++-- setup.py | 1 - tests/unit/client.py | 24 ++++----- 12 files changed, 186 insertions(+), 229 deletions(-) delete mode 100644 haas/client/auth.py delete mode 100644 haas/client/errors.py diff --git a/haas/cli.py b/haas/cli.py index 0f52968f..0ca5f246 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -23,12 +23,11 @@ import sys import urllib import schema -import abc from functools import wraps from haas.client.auth import db_auth, keystone_auth -from haas.client.client import Client +from haas.client.client import Client, RequestsHTTPClient, KeystoneHTTPClient from haas.client import errors @@ -38,92 +37,6 @@ MAX_PORT_NUMBER = 2**16 - 1 -class HTTPClient(object): - """An HTTP client. - - Makes HTTP requests on behalf of the HaaS CLI. Responsible for adding - authentication information to the request. - """ - - __metaclass__ = abc.ABCMeta - - @abc.abstractmethod - def request(method, url, data=None, params=None): - """Make an HTTP request - - Makes an HTTP request on URL `url` with method `method`, request body - `data`(if supplied) and query parameter `params`(if supplied). May add - authentication or other backend-specific information to the request. - - Parameters - ---------- - - method : str - The HTTP method to use, e.g. 'GET', 'PUT', 'POST'... - url : str - The URL to act on - data : str, optional - The body of the request - params : dictionary, optional - The query parameter, e.g. {'key1': 'val1', 'key2': 'val2'}, - dictionary key can't be `None` - - Returns - ------- - - requests.Response - The HTTP response - """ - - -class RequestsHTTPClient(requests.Session, HTTPClient): - """An HTTPClient which uses the requests library. - - Note that this doesn't do anything over `requests.Session`; that - class already implements the required interface. We declare it only - for clarity. - """ - - -class KeystoneHTTPClient(HTTPClient): - """An HTTPClient which authenticates with Keystone. - - This uses an instance of python-keystoneclient's Session class - to do its work. - """ - - def __init__(self, session): - """Create a KeystoneHTTPClient - - Parameters - ---------- - - session : keystoneauth1.Session - A keystone session to make the requests with - """ - self.session = session - - def request(self, method, url, data=None, params=None): - """Make an HTTP request using keystone for authentication. - - Smooths over the differences between python-keystoneclient's - request method that specified by HTTPClient - """ - # We have to import this here, since we can't assume the library - # is available from global scope. - from keystoneauth1.exceptions.http import HttpError - - try: - # The order of these parameters is different that what - # we expect, but the names are the same: - return self.session.request(method=method, - url=url, - data=data, - params=params) - except HttpError as e: - return e.response - - # An instance of HTTPClient, which will be used to make the request. http_client = None C = None @@ -200,13 +113,12 @@ def setup_http_client(): basic_username = os.getenv('HAAS_USERNAME') basic_password = os.getenv('HAAS_PASSWORD') if basic_username is not None and basic_password is not None: - # For calls using the client library - sess = db_auth(basic_username, basic_password) - C = Client(ep, sess) # For calls with no client library support yet. # Includes all headnode calls; registration of nodes and switches. http_client = RequestsHTTPClient() http_client.auth = (basic_username, basic_password) + # For calls using the client library + C = Client(ep, http_client) return # Next try keystone: try: @@ -218,17 +130,22 @@ def setup_http_client(): os_project_domain_id = os.getenv('OS_PROJECT_DOMAIN_ID') or 'default' if None in (os_auth_url, os_username, os_password, os_project_name): raise KeyError("Required openstack environment variable not set.") - sess = keystone_auth( - os_auth_url, os_username, os_password, os_user_domain_id, - os_project_name, os_project_domain_id - ) - C = Client(ep, sess) + auth = v3.Password(auth_url=os_auth_url, + username=os_username, + password=os_password, + project_name=os_project_name, + user_domain_id=os_user_domain_id, + project_domain_id=os_project_domain_id) + sess = session.Session(auth=auth) + http_client = KeystoneHTTPClient(sess) + # For calls using the client library + C = Client(ep, http_client) return except (ImportError, KeyError): pass # Finally, fall back to no authentication: - sess = requests.Session() - C = Client(ep, sess) + http_client = requests.Session() + C = Client(ep, http_client) class FailedAPICallException(Exception): diff --git a/haas/client/auth.py b/haas/client/auth.py deleted file mode 100644 index 50fc55da..00000000 --- a/haas/client/auth.py +++ /dev/null @@ -1,34 +0,0 @@ -import requests -import keystoneauth1.identity -from keystoneauth1.identity import v3 -from keystoneauth1 import session - - -def db_auth(username, password): - """For database as authentication backend, this function prepares - - the default client session. - """ - s = requests.Session() - s.auth = (username, password) - return s - - -def keystone_auth( - os_auth_url, os_username, os_password, os_user_domain_id, - os_project_name, os_project_domain_id - ): - """This functions setup requisites for the http session when using - - keystone as the aunthentication backend. - """ - auth = v3.Password( - auth_url=os_auth_url, username=os_username, password=os_password, - user_domain_id=os_user_domain_id, - project_name=os_project_name, - project_domain_id=os_project_domain_id - ) - - s = session.Session(auth=auth) - - return s diff --git a/haas/client/base.py b/haas/client/base.py index cf7897ea..57e2f1e1 100644 --- a/haas/client/base.py +++ b/haas/client/base.py @@ -3,10 +3,13 @@ import requests import os import json -from haas.client import errors from urlparse import urljoin +class FailedAPICallException(Exception): + pass + + class ClientBase(object): """Main class which contains all the methods to @@ -17,7 +20,7 @@ class ClientBase(object): appropriate message. """ - def __init__(self, endpoint=None, sess=None): + def __init__(self, endpoint, httpClient): """ Initialize an instance of the library with following parameters. endpoint: stands for the http endpoint eg. endpoint=http://127.0.0.1 @@ -27,13 +30,7 @@ def __init__(self, endpoint=None, sess=None): Currently all this information is fetched from the user's environment. """ self.endpoint = endpoint - self.s = sess - - if None in [self.endpoint, self.s]: - raise LookupError( - "Incomplete client parameters (username, password, etc) " - "supplied to set up connection with HaaS server" - ) + self.httpClient = httpClient def object_url(self, *args): """Generate URL from combining endpoint and args as relative URL""" @@ -49,4 +46,4 @@ def check_response(self, response): return else: e = response.json() - raise errors.FailedAPICallException(e['msg']) + raise FailedAPICallException(e['msg']) diff --git a/haas/client/client.py b/haas/client/client.py index 607f5297..a274080b 100644 --- a/haas/client/client.py +++ b/haas/client/client.py @@ -5,17 +5,104 @@ from haas.client.switch import Port from haas.client.network import Network from haas.client.user import User -from haas.client import auth +import abc +import requests + + +class HTTPClient(object): + """An HTTP client. + + Makes HTTP requests on behalf of the HaaS CLI. Responsible for adding + authentication information to the request. + """ + + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def request(method, url, data=None, params=None): + """Make an HTTP request + + Makes an HTTP request on URL `url` with method `method`, request body + `data`(if supplied) and query parameter `params`(if supplied). May add + authentication or other backend-specific information to the request. + + Parameters + ---------- + + method : str + The HTTP method to use, e.g. 'GET', 'PUT', 'POST'... + url : str + The URL to act on + data : str, optional + The body of the request + params : dictionary, optional + The query parameter, e.g. {'key1': 'val1', 'key2': 'val2'}, + dictionary key can't be `None` + + Returns + ------- + + requests.Response + The HTTP response + """ + + +class RequestsHTTPClient(requests.Session, HTTPClient): + """An HTTPClient which uses the requests library. + + Note that this doesn't do anything over `requests.Session`; that + class already implements the required interface. We declare it only + for clarity. + """ + + +class KeystoneHTTPClient(HTTPClient): + """An HTTPClient which authenticates with Keystone. + + This uses an instance of python-keystoneclient's Session class + to do its work. + """ + + def __init__(self, session): + """Create a KeystoneHTTPClient + + Parameters + ---------- + + session : keystoneauth1.Session + A keystone session to make the requests with + """ + self.session = session + + def request(self, method, url, data=None, params=None): + """Make an HTTP request using keystone for authentication. + + Smooths over the differences between python-keystoneclient's + request method that specified by HTTPClient + """ + # We have to import this here, since we can't assume the library + # is available from global scope. + from keystoneauth1.exceptions.http import HttpError + + try: + # The order of these parameters is different that what + # we expect, but the names are the same: + return self.session.request(method=method, + url=url, + data=data, + params=params) + except HttpError as e: + return e.response class Client(object): - def __init__(self, endpoint, sess): + def __init__(self, endpoint, httpClient): + self.httpClient = httpClient self.endpoint = endpoint - self.s = sess - self.node = Node(self.endpoint, self.s) - self.project = Project(self.endpoint, self.s) - self.switch = Switch(self.endpoint, self.s) - self.port = Port(self.endpoint, self.s) - self.network = Network(self.endpoint, self.s) - self.user = User(self.endpoint, self.s) + self.node = Node(self.endpoint, self.httpClient) + self.project = Project(self.endpoint, self.httpClient) + self.switch = Switch(self.endpoint, self.httpClient) + self.port = Port(self.endpoint, self.httpClient) + self.network = Network(self.endpoint, self.httpClient) + self.user = User(self.endpoint, self.httpClient) diff --git a/haas/client/errors.py b/haas/client/errors.py deleted file mode 100644 index 45befc52..00000000 --- a/haas/client/errors.py +++ /dev/null @@ -1,22 +0,0 @@ -class AuthenticationError(Exception): - pass - - -class NotFoundError(Exception): - pass - - -class DuplicateError(Exception): - pass - - -class BlockedError(Exception): - pass - - -class ProjectMismatchError(Exception): - pass - - -class FailedAPICallException(Exception): - pass diff --git a/haas/client/network.py b/haas/client/network.py index 4d00d13f..58158fa1 100644 --- a/haas/client/network.py +++ b/haas/client/network.py @@ -1,6 +1,5 @@ import json from haas.client.base import ClientBase -from haas.client import errors class Network(ClientBase): @@ -12,12 +11,12 @@ class Network(ClientBase): def list(self): """Lists all projects under HIL """ url = self.object_url('networks') - return self.check_response(self.s.get(url)) + return self.check_response(self.httpClient.request("GET", url)) def show(self, network): """Shows attributes of a network. """ url = self.object_url('network', network) - return self.check_response(self.s.get(url)) + return self.check_response(self.httpClient.request("GET", url)) def create(self, network, owner, access, net_id): """Create a link-layer . @@ -29,23 +28,25 @@ def create(self, network, owner, access, net_id): 'owner': owner, 'access': access, 'net_id': net_id }) - return self.check_response(self.s.put(url, data=payload)) + return self.check_response( + self.httpClient.request("PUT", url, data=payload) + ) def delete(self, network): """Delete a . """ url = self.object_url('network', network) - return self.check_response(self.s.delete(url)) + return self.check_response(self.httpClient.request("DELETE", url)) def grant_access(self, project, network): """Grants access to . """ url = self.object_url( 'network', network, 'access', project ) - return self.check_response(self.s.put(url)) + return self.check_response(self.httpClient.request("PUT", url)) def revoke_access(self, project, network): """Removes access of from . """ url = self.object_url( 'network', network, 'access', project ) - return self.check_response(self.s.delete(url)) + return self.check_response(self.httpClient.request("DELETE", url)) diff --git a/haas/client/node.py b/haas/client/node.py index a92895de..5aa0559a 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -1,6 +1,5 @@ import json from haas.client.base import ClientBase -from haas.client import errors class Node(ClientBase): @@ -12,13 +11,13 @@ class Node(ClientBase): def list(self, is_free): """List all nodes that HIL manages """ url = self.object_url('nodes', is_free) - return self.check_response(self.s.get(url)) + return self.check_response(self.httpClient.request('GET', url)) def show(self, node_name): """Shows attributes of a given node """ url = self.object_url('node', node_name) - return self.check_response(self.s.get(url)) + return self.check_response(self.httpClient.request('GET', url)) def register(self, node, subtype, *args): """Register a node with appropriate OBM driver. """ @@ -33,33 +32,35 @@ def register(self, node, subtype, *args): # FIXME: In future obm_types should be dynamically fetched # from haas.cfg, need a new api call for querying available # and currently active drivers for HIL -# Not implemented yet. + raise NotImplementedError def delete(self, node_name): """Deletes the node from database. """ url = self.object_url('node', node_name) - return check_response(self.s.delete(url)) + return check_response(self.httpClient.request('DELETE', url)) def power_cycle(self, node_name): """Power cycles the """ url = self.object_url('node', node_name, 'power_cycle') - return self.check_response(self.s.post(url)) + return self.check_response(self.httpClient.request('POST', url)) def power_off(self, node_name): """Power offs the """ url = self.object_url('node', node_name, 'power_off') - return self.check_response(self.s.post(url)) + return self.check_response(self.httpClient.request('POST', url)) def add_nic(self, node_name, nic_name, macaddr): """Add a to """ url = self.object_url('node', node_name, 'nic', nic_name) payload = json.dumps({'macaddr': macaddr}) - return self.check_response(self.s.put(url, data=payload)) + return self.check_response( + self.httpClient.request('PUT', url, data=payload) + ) def remove_nic(self, node_name, nic_name): """Remove a from """ url = self.object_url('node', node_name, 'nic', nic_name) - return self.check_response(self.s.delete(url)) + return self.check_response(self.httpClient.request('DELETE', url)) def connect_network(self, node, nic, network, channel): """Connect to on given and """ @@ -69,7 +70,9 @@ def connect_network(self, node, nic, network, channel): payload = json.dumps({ 'network': network, 'channel': channel }) - return self.check_response(self.s.post(url, payload)) + return self.check_response( + self.httpClient.request('POST', url, data=payload) + ) def detach_network(self, node, nic, network): """Disconnect from on the given . """ @@ -77,18 +80,20 @@ def detach_network(self, node, nic, network): 'node', node, 'nic', nic, 'detach_network' ) payload = json.dumps({'network': network}) - return self.check_response(self.s.post(url, payload)) + return self.check_response( + self.httpClient.request('PUT', url, payload) + ) def show_console(self, node): """Display console log for """ - # Not implemented yet. + raise NotImplementedError def start_console(self, node): """Start logging console output from """ url = self.object_url('node', node, 'console') - return self.check_response(self.s.put(url)) + return self.check_response(self.httpClient.request('PUT', url)) def stop_console(self, node): """Stop logging console output from and delete the log""" url = self.object_url('node', node, 'console') - return self.check_response(self.s.delete(url)) + return self.check_response(self.httpClient.request('DELETE', url)) diff --git a/haas/client/project.py b/haas/client/project.py index 6a6ade76..e04a3b93 100644 --- a/haas/client/project.py +++ b/haas/client/project.py @@ -1,6 +1,5 @@ import json from haas.client.base import ClientBase -from haas.client import errors class Project(ClientBase): @@ -13,29 +12,29 @@ def list(self): """Lists all projects under HIL """ url = self.object_url('/projects') - return self.check_response(self.s.get(url)) + return self.check_response(self.httpClient.request("GET", url)) def nodes_in(self, project_name): """Lists nodes allocated to project """ url = self.object_url('project', project_name, 'nodes') - return self.check_response(self.s.get(url)) + return self.check_response(self.httpClient.request("GET", url)) def networks_in(self, project_name): """Lists nodes allocated to project """ url = self.object_url( 'project', project_name, 'networks' ) - return self.check_response(self.s.get(url)) + return self.check_response(self.httpClient.request("GET", url)) def create(self, project_name): """Creates a project named """ url = self.object_url('project', project_name) - return self.check_response(self.s.put(url)) + return self.check_response(self.httpClient.request("PUT", url)) def delete(self, project_name): """Deletes a project named """ url = self.object_url('project', project_name) - return self.check_response(self.s.delete(url)) + return self.check_response(self.httpClient.request("DELETE", url)) def connect(self, project_name, node_name): """Adds a node to a project. """ @@ -44,7 +43,7 @@ def connect(self, project_name, node_name): ) self.payload = json.dumps({'node': node_name}) return self.check_response( - self.s.post(url, data=self.payload) + self.httpClient.request("POST", url, data=self.payload) ) def detach(self, project_name, node_name): @@ -52,5 +51,5 @@ def detach(self, project_name, node_name): url = self.object_url('project', project_name, 'detach_node') self.payload = json.dumps({'node': node_name}) return self.check_response( - self.s.post(url, data=self.payload) + self.httpClient.request("POST", url, data=self.payload) ) diff --git a/haas/client/switch.py b/haas/client/switch.py index 80b48353..5923ad1c 100644 --- a/haas/client/switch.py +++ b/haas/client/switch.py @@ -1,6 +1,5 @@ import json from haas.client.base import ClientBase -from haas.client import errors class Switch(ClientBase): @@ -12,7 +11,7 @@ class Switch(ClientBase): def list(self): """List all nodes that HIL manages """ url = self.object_url('/switches') - return self.check_response(self.s.get(url)) + return self.check_response(self.httpClient.request("GET", url)) def register(self, switch, subtype, *args): """Registers a switch with name and @@ -22,16 +21,16 @@ def register(self, switch, subtype, *args): # of the switches HIL will control and has read the # HIL documentation to use appropriate flags to register # it with HIL. -# Not Implemented yet. + raise NotImplementedError def delete(self, switch): url = self.object_url('switch', switch) - return self.check_response(self.s.delete(url)) + return self.check_response(self.httpClient.request("DELETE", url)) def show(self, switch): """Shows attributes of . """ url = self.object_url('switch', switch) - return self.check_response(self.s.get(url)) + return self.check_response(self.httpClient.request("GET", url)) class Port(ClientBase): @@ -40,20 +39,22 @@ class Port(ClientBase): def register(self, switch, port): """Register a with . """ url = self.object_url('switch', switch, 'port', port) - return self.check_response(self.s.put(url)) + return self.check_response(self.httpClient.request("PUT", url)) def delete(self, switch, port): """Deletes information of the for """ url = self.object_url('switch', switch, 'port', port) - return self.check_response(self.s.delete(url)) + return self.check_response(self.httpClient.request("DELETE", url)) def connect_nic(self, switch, port, node, nic): """Connects of to of . """ url = self.object_url('switch', switch, 'port', port, 'connect_nic') payload = json.dumps({'node': node, 'nic': nic}) - return self.check_response(self.s.post(url, payload)) + return self.check_response( + self.httpClient.request("POST", url, data=payload) + ) def detach_nic(self, switch, port): """"Detaches of . """ url = self.object_url('switch', switch, 'port', port, 'detach_nic') - return self.check_response(self.s.post(url)) + return self.check_response(self.httpClient.request("POST", url)) diff --git a/haas/client/user.py b/haas/client/user.py index 512bda63..7087097f 100644 --- a/haas/client/user.py +++ b/haas/client/user.py @@ -1,6 +1,5 @@ import json from haas.client.base import ClientBase -from haas.client import errors class User(ClientBase): @@ -25,21 +24,29 @@ def create(self, username, password, privilege): payload = json.dumps({ 'password': password, 'is_admin': privilege == 'admin', }) - return self.check_response(self.s.put(url, data=payload)) + return self.check_response( + self.httpClient.request("PUT", url, data=payload) + ) def delete(self, username): """Deletes the user . """ url = self.object_url('/auth/basic/user', username) - return self.check_response(self.s.delete(url)) + return self.check_response( + self.httpClient.request("DELETE", url) + ) def add(self, user, project): """Adds to a . """ url = self.object_url('/auth/basic/user', user, 'add_project') payload = json.dumps({'project': project}) - return self.check_response(self.s.post(url, data=payload)) + return self.check_response( + self.httpClient.request("POST", url, data=payload) + ) def remove(self, user, project): """Removes all access of to . """ url = self.object_url('/auth/basic/user', user, 'remove_project') payload = json.dumps({'project': project}) - return self.check_response(self.s.post(url, data=payload)) + return self.check_response( + self.httpClient.request("POST", url, data=payload) + ) diff --git a/setup.py b/setup.py index e4065357..5d416ae2 100644 --- a/setup.py +++ b/setup.py @@ -99,5 +99,4 @@ def _get_readme(): 'pep8>=1.7.0', 'requests_mock>=1.0.0,<2.0', 'lxml>=3.6.0,<4.0', - 'keystoneauth1>=2.16,<3.0', ]) diff --git a/tests/unit/client.py b/tests/unit/client.py index 346e09fb..54955721 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -34,7 +34,7 @@ from haas.client import errors -ep = "http://127.0.0.1:8888" or os.environ.get('HAAS_ENDPOINT') +ep = "http://127.0.0.1:8000" or os.environ.get('HAAS_ENDPOINT') username = "hil_user" or os.environ.get('HAAS_USERNAME') password = "hil_pass1234" or os.environ.get('HAAS_PASSWORD') @@ -60,19 +60,19 @@ class Test_ClientBase: def test_init_error(self): try: x = ClientBase() - except LookupError: + except TypeError: assert True # FIX ME: The test may vary based on which backend session is used. # def test_correct_init(self): # x = ClientBase(ep, 'some_base64_string') -# assert x.endpoint == "http://127.0.0.1:8888" +# assert x.endpoint == "http://127.0.0.1:8000" # assert x.auth == "some_base64_string" def test_object_url(self): x = ClientBase(ep, 'some_base64_string') y = x.object_url('abc', '123', 'xy23z') - assert y == 'http://127.0.0.1:8888/abc/123/xy23z' + assert y == 'http://127.0.0.1:8000/abc/123/xy23z' # For testing the client library we need a running HIL server, with dummy # objects populated. Following classes accomplish that end. @@ -144,7 +144,7 @@ def initialize_db(): def run_server(cmd): """This function starts a haas server. The arguments in 'cmd' will be a list of arguments like required to start a - haas server like ['haas', 'serve', '8888'] + haas server like ['haas', 'serve', '8000'] It will return a handle which can be used to terminate the server when tests finish. """ @@ -161,7 +161,7 @@ def populate_server(): sess.auth = (username, password) # Adding nodes, node-01 - node-06 - url_node = 'http://127.0.0.1:8888/node/' + url_node = 'http://127.0.0.1:8000/node/' api_nodename = 'http://schema.massopencloud.org/haas/v0/obm/' for i in range(1, 10): @@ -180,10 +180,10 @@ def populate_server(): # Adding Projects proj-01 - proj-03 for i in ["proj-01", "proj-02", "proj-03"]: - sess.put('http://127.0.0.1:8888/project/' + i) + sess.put('http://127.0.0.1:8000/project/' + i) # Adding switches one for each driver - url_switch = 'http://127.0.0.1:8888/switch/' + url_switch = 'http://127.0.0.1:8000/switch/' api_name = 'http://schema.massopencloud.org/haas/v0/switches/' dell_param = { @@ -224,10 +224,10 @@ def populate_server(): # Adding Projects proj-01 - proj-03 for i in ["proj-01", "proj-02", "proj-03"]: - sess.put('http://127.0.0.1:8888/project/' + i) + sess.put('http://127.0.0.1:8000/project/' + i) # Allocating nodes to projects - url_project = 'http://127.0.0.1:8888/project/' + url_project = 'http://127.0.0.1:8000/project/' # Adding nodes 1 to proj-01 sess.post( url_project + 'proj-01' + '/connect_node', @@ -253,7 +253,7 @@ def populate_server(): ) # Assigning networks to projects - url_network = 'http://127.0.0.1:8888/network/' + url_network = 'http://127.0.0.1:8000/network/' for i in ['net-01', 'net-02', 'net-03']: sess.put( url_network + i, @@ -276,7 +276,7 @@ def populate_server(): def create_setup(request): dir_names = make_config() initialize_db() - proc1 = run_server(['haas', 'serve', '8888']) + proc1 = run_server(['haas', 'serve', '8000']) proc2 = run_server(['haas', 'serve_networks']) time.sleep(1) populate_server() From 42bd4dda600072718af6a0a1d4d4bc531289805b Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 20 Mar 2017 13:24:39 -0400 Subject: [PATCH 57/71] Fixes failing tests, removes extraneous import statements. Updates doc-string to reflect current state of function in cli.py --- haas/cli.py | 8 ++++++-- tests/unit/client.py | 15 ++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 0ca5f246..796e03ec 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -26,9 +26,7 @@ from functools import wraps -from haas.client.auth import db_auth, keystone_auth from haas.client.client import Client, RequestsHTTPClient, KeystoneHTTPClient -from haas.client import errors command_dict = {} @@ -87,6 +85,8 @@ def wrapped(*args, **kwargs): def setup_http_client(): """Set `http_client` to a valid instance of `HTTPClient` + and pass it as parameter to initialize the client library. + Sets http_client to an object which makes HTTP requests with authentication. It chooses an authentication backend as follows: @@ -105,6 +105,10 @@ def setup_http_client(): 3. Oterwise, do not supply authentication information. This may be extended with other backends in the future. + + `http_client` is also passed as a parameter to the client library. + Until all calls are moved to client library, this will support + both ways of intereacting with HIL. """ global http_client global C # initiating the client library diff --git a/tests/unit/client.py b/tests/unit/client.py index 54955721..302c66e5 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -29,8 +29,7 @@ import requests from requests.exceptions import ConnectionError from haas.client.base import ClientBase -from haas.client.auth import db_auth -from haas.client.client import Client +from haas.client.client import Client, RequestsHTTPClient, KeystoneHTTPClient from haas.client import errors @@ -38,20 +37,14 @@ username = "hil_user" or os.environ.get('HAAS_USERNAME') password = "hil_pass1234" or os.environ.get('HAAS_PASSWORD') -sess = db_auth(username, password) -C = Client(ep, sess) # Initializing client library +http_client = RequestsHTTPClient() +http_client.auth = (username, password) +C = Client(ep, http_client) # Initializing client library MOCK_SWITCH_TYPE = 'http://schema.massopencloud.org/haas/v0/switches/mock' OBM_TYPE_MOCK = 'http://schema.massopencloud.org/haas/v0/obm/mock' OBM_TYPE_IPMI = 'http://schema.massopencloud.org/haas/v0/obm/ipmi' -# Following tests check if the client library is initialized correctly - -def test_db_auth(): - sess = db_auth(username, password) - assert sess.auth == (username, password) - - class Test_ClientBase: """ When the username, password is not defined It should raise a LookupError From c7195a1b248449dca95c7c709691a4ddcf4503e3 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 20 Mar 2017 14:40:10 -0400 Subject: [PATCH 58/71] minor changes --- tests/unit/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 302c66e5..b359c4c2 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -30,7 +30,6 @@ from requests.exceptions import ConnectionError from haas.client.base import ClientBase from haas.client.client import Client, RequestsHTTPClient, KeystoneHTTPClient -from haas.client import errors ep = "http://127.0.0.1:8000" or os.environ.get('HAAS_ENDPOINT') From 67ba23841987400544b2a0d0f4c2bbcc6b6186ab Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 22 Mar 2017 14:42:47 -0400 Subject: [PATCH 59/71] Fixes cli integration test, Puts appropriate comments in cli.py --- haas/cli.py | 8 ++++++-- tests/integration/cli.py | 3 --- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 796e03ec..14a2972c 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -365,13 +365,13 @@ def headnode_delete(headnode): @cmd def project_connect_node(project, node): """Connect to """ - check_clientlib_response(C.project.connect, project, node) + check_clientlib_response(lambda: C.project.connect(project, node)) @cmd def project_detach_node(project, node): """Detach from """ - check_clientlib_response(C.project.detach, project, node) + check_clientlib_response(lambda: C.project.detach(project, node)) @cmd @@ -695,7 +695,11 @@ def show_network(network): @cmd def show_node(node): """Display information about a + FIXME: Recursion should be implemented to the output. + The output of show_node is a dictionary that can be list of list, having + multiple nics and networks. More meta data about node could be shown + via this call. So this is a note for developers that will work on CLI. """ q = check_clientlib_response(lambda: C.node.show(node)) for item in q.items(): diff --git a/tests/integration/cli.py b/tests/integration/cli.py index 55e08140..1933e0ae 100644 --- a/tests/integration/cli.py +++ b/tests/integration/cli.py @@ -18,13 +18,11 @@ def test_add_list_delete_projects(): projects = output.split(" ") assert PROJECT1 not in projects assert PROJECT2 not in projects - projects.remove('\n') # remove \n from list size = len(projects) subprocess.check_call(['haas', 'project_create', PROJECT1]) output = subprocess.check_output(['haas', 'list_projects']) projects = output.split(" ") - projects = map(str.strip, projects) # strips off \n from project name assert PROJECT1 in projects assert PROJECT2 not in projects assert len(projects) == size + 1 @@ -32,7 +30,6 @@ def test_add_list_delete_projects(): subprocess.check_call(['haas', 'project_delete', PROJECT1]) output = subprocess.check_output(['haas', 'list_projects']) projects = output.split(" ") - projects.remove('\n') # remove \n from list assert PROJECT1 not in projects assert PROJECT2 not in projects assert len(projects) == size From f48e608190d1a78719911af1df892db3dc217dc0 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Thu, 23 Mar 2017 07:49:04 -0400 Subject: [PATCH 60/71] minor changes, moving text from docstring to comment --- haas/cli.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 14a2972c..d28938a9 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -697,10 +697,12 @@ def show_node(node): """Display information about a FIXME: Recursion should be implemented to the output. - The output of show_node is a dictionary that can be list of list, having - multiple nics and networks. More meta data about node could be shown - via this call. So this is a note for developers that will work on CLI. """ +# The output of show_node is a dictionary that can be list of list, having +# multiple nics and networks. More metadata about node could be shown +# via this call. Suggestion to future developers of CLI to use +# recursion in the call for output of such metadata. + q = check_clientlib_response(lambda: C.node.show(node)) for item in q.items(): sys.stdout.write("%s\t : %s\n" % (item[0], item[1])) From 696f3efc06c1253ade41303e8a51f0fabaabfd48 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 27 Mar 2017 08:08:26 -0400 Subject: [PATCH 61/71] fixes integration tests --- tests/integration/cli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/cli.py b/tests/integration/cli.py index 1933e0ae..477fc8ef 100644 --- a/tests/integration/cli.py +++ b/tests/integration/cli.py @@ -16,12 +16,14 @@ def test_add_list_delete_projects(): The project list does not need to be empty before running the test.""" output = subprocess.check_output(['haas', 'list_projects']) projects = output.split(" ") + projects.remove('\n') assert PROJECT1 not in projects assert PROJECT2 not in projects size = len(projects) subprocess.check_call(['haas', 'project_create', PROJECT1]) output = subprocess.check_output(['haas', 'list_projects']) + output = output.strip('\n') projects = output.split(" ") assert PROJECT1 in projects assert PROJECT2 not in projects @@ -30,6 +32,7 @@ def test_add_list_delete_projects(): subprocess.check_call(['haas', 'project_delete', PROJECT1]) output = subprocess.check_output(['haas', 'list_projects']) projects = output.split(" ") + projects.remove('\n') assert PROJECT1 not in projects assert PROJECT2 not in projects assert len(projects) == size From 5786e3726662e55b304c07c033956244f6182f78 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Sat, 1 Apr 2017 23:32:10 -0400 Subject: [PATCH 62/71] Addresses all the changes recommended by reviewers. --- haas/cli.py | 6 +- haas/client/node.py | 4 +- tests/unit/client.py | 216 ++++++++++++++++--------------------------- 3 files changed, 85 insertions(+), 141 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index d28938a9..b22b88f0 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -160,7 +160,7 @@ def check_clientlib_response(fun): try: return fun() except Exception as e: - sys.stderr.write('Error: %s\n' % e.message) + sys.exit('Error: %s\n' % e.message) def check_status_code(response): @@ -739,13 +739,13 @@ def show_console(node): @cmd def start_console(node): """Start logging console output from """ - q = check_clientlib_response(lambda: C.node.start_console(node)) + check_clientlib_response(lambda: C.node.start_console(node)) @cmd def stop_console(node): """Stop logging console output from and delete the log""" - q = check_clientlib_response(lambda: C.node.stop_console(node)) + check_clientlib_response(lambda: C.node.stop_console(node)) @cmd diff --git a/haas/client/node.py b/haas/client/node.py index 5aa0559a..1d0d924c 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -29,8 +29,8 @@ def register(self, node, subtype, *args): obm_api = "http://schema.massopencloud.org/haas/v0/obm/" obm_types = ["ipmi", "mock"] -# FIXME: In future obm_types should be dynamically fetched -# from haas.cfg, need a new api call for querying available +# FIXME: In future obm_types should be dynamically fetched. +# We need a new api call for querying available # and currently active drivers for HIL raise NotImplementedError diff --git a/tests/unit/client.py b/tests/unit/client.py index b359c4c2..b73ea7d2 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -45,21 +45,11 @@ class Test_ClientBase: - """ When the username, password is not defined - It should raise a LookupError - """ + """Tests client initialization and object_url creation. """ def test_init_error(self): - try: + with pytest.raises(Exception): x = ClientBase() - except TypeError: - assert True - -# FIX ME: The test may vary based on which backend session is used. -# def test_correct_init(self): -# x = ClientBase(ep, 'some_base64_string') -# assert x.endpoint == "http://127.0.0.1:8000" -# assert x.auth == "some_base64_string" def test_object_url(self): x = ClientBase(ep, 'some_base64_string') @@ -76,8 +66,6 @@ def test_object_url(self): # 5. tears down the setup in a clean fashion. -# pytest.fixture(scope="module") - def make_config(): """ This function creates haas.cfg with desired options and writes to a temporary directory. @@ -133,15 +121,21 @@ def initialize_db(): check_call(['haas', 'create_admin_user', username, password]) -def run_server(cmd): - """This function starts a haas server. - The arguments in 'cmd' will be a list of arguments like required to start a - haas server like ['haas', 'serve', '8000'] - It will return a handle which can be used to terminate the server when - tests finish. +# Allocating nodes to projects +def assign_nodes2project(sess, project, *nodes): + """ Assigns multiple to a . + + Takes as input + : session object for REST call, : project name, + <*nodes> one or more node names. """ - proc = Popen(cmd) - return proc + url_project = 'http://127.0.0.1:8000/project/' + + for node in nodes: + sess.post( + url_project + project + '/connect_node', + data=json.dumps({'node': node}) + ) def populate_server(): @@ -152,13 +146,13 @@ def populate_server(): sess = requests.Session() sess.auth = (username, password) - # Adding nodes, node-01 - node-06 + # Adding nodes, node-01 - node-09 url_node = 'http://127.0.0.1:8000/node/' - api_nodename = 'http://schema.massopencloud.org/haas/v0/obm/' + ipmi = 'http://schema.massopencloud.org/haas/v0/obm/ipmi' for i in range(1, 10): obminfo = { - "type": api_nodename + 'ipmi', "host": "10.10.0.0"+repr(i), + "type": ipmi, "host": "10.10.0.0"+repr(i), "user": "ipmi_u", "password": "pass1234" } sess.put( @@ -219,30 +213,9 @@ def populate_server(): sess.put('http://127.0.0.1:8000/project/' + i) # Allocating nodes to projects - url_project = 'http://127.0.0.1:8000/project/' - # Adding nodes 1 to proj-01 - sess.post( - url_project + 'proj-01' + '/connect_node', - data=json.dumps({'node': 'node-01'}) - ) - # Adding nodes 2, 4 to proj-02 - sess.post( - url_project + 'proj-02' + '/connect_node', - data=json.dumps({'node': 'node-02'}) - ) - sess.post( - url_project + 'proj-02' + '/connect_node', - data=json.dumps({'node': 'node-04'}) - ) - # Adding node 3, 5 to proj-03 - sess.post( - url_project + 'proj-03' + '/connect_node', - data=json.dumps({'node': 'node-03'}) - ) - sess.post( - url_project + 'proj-03' + '/connect_node', - data=json.dumps({'node': 'node-05'}) - ) + assign_nodes2project(sess, 'proj-01', 'node-01') + assign_nodes2project(sess, 'proj-02', 'node-02', 'node-04') + assign_nodes2project(sess, 'proj-03', 'node-03', 'node-05') # Assigning networks to projects url_network = 'http://127.0.0.1:8000/network/' @@ -263,22 +236,19 @@ def populate_server(): ) -# -- SETUP -- @pytest.fixture(scope="module") def create_setup(request): dir_names = make_config() initialize_db() - proc1 = run_server(['haas', 'serve', '8000']) - proc2 = run_server(['haas', 'serve_networks']) + proc1 = Popen(['haas', 'serve', '8000']) + proc2 = Popen(['haas', 'serve_networks']) time.sleep(1) populate_server() - print("coming from create_setup") def fin(): proc1.terminate() proc2.terminate() cleanup(dir_names) - print("tearing down HIL setup") request.addfinalizer(fin) @@ -287,19 +257,18 @@ class Test_node: """ Tests Node related client calls. """ def test_list_nodes_free(self): - result = C.node.list('free') - assert result == [u'node-06', u'node-07', u'node-08', u'node-09'] + assert C.node.list('free') == [ + u'node-06', u'node-07', u'node-08', u'node-09' + ] def test_list_nodes_all(self): - result = C.node.list('all') - assert result == [ + assert C.node.list('all') == [ u'node-01', u'node-02', u'node-03', u'node-04', u'node-05', u'node-06', u'node-07', u'node-08', u'node-09' ] def test_show_node(self): - result = C.node.show('node-07') - assert result == { + assert C.node.show('node-07') == { u'metadata': {}, u'project': None, u'nics': [ @@ -312,17 +281,14 @@ def test_show_node(self): } def test_power_cycle(self): - result = C.node.power_cycle('node-07') - assert result is None + assert C.node.power_cycle('node-07') is None def test_power_off(self): - result = C.node.power_off('node-07') - assert result is None + assert C.node.power_off('node-07') is None def test_node_add_nic(self): C.node.remove_nic('node-08', 'eth0') - result = C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') - assert result is None + assert C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') is None def test_node_add_duplicate_nic(self): C.node.remove_nic('node-08', 'eth0') @@ -335,8 +301,7 @@ def test_nosuch_node_add_nic(self): C.node.add_nic('abcd', 'eth0', 'aa:bb:cc:dd:ee:ff') def test_remove_nic(self): - result = C.node.remove_nic('node-08', 'eth0') - assert result is None + assert C.node.remove_nic('node-08', 'eth0') is None def test_remove_duplicate_nic(self): C.node.remove_nic('node-08', 'eth0') @@ -344,26 +309,29 @@ def test_remove_duplicate_nic(self): C.node.remove_nic('node-08', 'eth0') def test_node_connect_network(self): - result = C.node.connect_network( + assert C.node.connect_network( 'node-01', 'eth0', 'net-01', 'vlan/native' - ) - assert result is None + ) is None def test_node_start_console(self): - result = C.node.start_console('node-01') - assert result is None + assert C.node.start_console('node-01') is None def test_node_stop_console(self): - result = C.node.stop_console('node-01') - assert result is None + assert C.node.stop_console('node-01') is None # FIXME: I spent some time on this test. Looks like the pytest # framework kills the network server before it can detach network. -# def test_node_detach_network(self): -# C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') -# result = C.node.detach_network('node-04', 'eth0', 'net-04') -# assert result is None +# Explanation: The test is unreliable because of following reasons. +# When using a real switch driver for network, haas network server +# times out waiting for response from switch while using mock switch +# driver, the networking_action queue takes longer to execute the request +# the testing server kills the servers when other tests are completed. + + @pytest.mark.xfail + def test_node_detach_network(self): + C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') + assert C.node.detach_network('node-04', 'eth0', 'net-04') is None @pytest.mark.usefixtures("create_setup") @@ -372,25 +340,22 @@ class Test_project: def test_list_projects(self): """ test for getting list of project """ - result = C.project.list() - assert result == [u'proj-01', u'proj-02', u'proj-03'] + assert C.project.list() == [u'proj-01', u'proj-02', u'proj-03'] def test_list_nodes_inproject(self): """ test for getting list of nodes connected to a project. """ - result01 = C.project.nodes_in('proj-01') - result02 = C.project.nodes_in('proj-02') - assert result01 == [u'node-01'] - assert result02 == [u'node-02', u'node-04'] + assert C.project.nodes_in('proj-01') == [u'node-01'] + assert C.project.nodes_in('proj-02') == [u'node-02', u'node-04'] def test_list_networks_inproject(self): """ test for getting list of networks connected to a project. """ - result = C.project.networks_in('proj-01') - assert result == [u'net-01', u'net-02', u'net-03'] + assert C.project.networks_in('proj-01') == [ + u'net-01', u'net-02', u'net-03' + ] def test_project_create(self): """ test for creating project. """ - result = C.project.create('dummy-01') - assert result is None + assert C.project.create('dummy-01') is None def test_duplicate_project_create(self): """ test for catching duplicate name while creating new project. """ @@ -401,8 +366,7 @@ def test_duplicate_project_create(self): def test_project_delete(self): """ test for deleting project. """ C.project.create('dummy-03') - result = C.project.delete('dummy-03') - assert result is None + assert C.project.delete('dummy-03') is None def test_error_project_delete(self): """ test to capture error condition in project delete. """ @@ -412,8 +376,7 @@ def test_error_project_delete(self): def test_project_connect_node(self): """ test for connecting node to project. """ C.project.create('proj-04') - result = C.project.connect('proj-04', 'node-06') - assert result is None + assert C.project.connect('proj-04', 'node-06') is None def test_project_connect_node_duplicate(self): """ test for erronous reconnecting node to project. """ @@ -434,8 +397,7 @@ def test_project_detach_node(self): """ Test for correctly detaching node from project.""" C.project.create('proj-07') C.project.connect('proj-07', 'node-08') - result = C.project.detach('proj-07', 'node-08') - assert result is None + assert C.project.detach('proj-07', 'node-08') is None def test_project_detach_node_nosuchobject(self): """ Test for while detaching node from project.""" @@ -451,16 +413,15 @@ class Test_switch: """ Tests switch related client calls.""" def test_list_switches(self): - result = C.switch.list() - assert result == [u'brocade-01', u'dell-01', u'mock-01', u'nexus-01'] + assert C.switch.list() == [ + u'brocade-01', u'dell-01', u'mock-01', u'nexus-01' + ] def test_show_switch(self): - result = C.switch.show('dell-01') - assert result == {u'name': u'dell-01', u'ports': []} + assert C.switch.show('dell-01') == {u'name': u'dell-01', u'ports': []} def test_delete_switch(self): - result = C.switch.delete('nexus-01') - assert result is None + assert C.switch.delete('nexus-01') is None @pytest.mark.usefixtures("create_setup") @@ -468,8 +429,7 @@ class Test_port: """ Tests port related client calls.""" def test_port_register(self): - result = C.port.register('mock-01', 'gi1/1/1') - assert result is None + assert C.port.register('mock-01', 'gi1/1/1') is None def test_port_dupregister(self): C.port.register('mock-01', 'gi1/1/2') @@ -478,8 +438,7 @@ def test_port_dupregister(self): def test_port_delete(self): C.port.register('mock-01', 'gi1/1/3') - result = C.port.delete('mock-01', 'gi1/1/3') - assert result is None + assert C.port.delete('mock-01', 'gi1/1/3') is None def test_port_delete_error(self): C.port.register('mock-01', 'gi1/1/4') @@ -489,8 +448,9 @@ def test_port_delete_error(self): def test_port_connect_nic(self): C.port.register('mock-01', 'gi1/1/5') - result = C.port.connect_nic('mock-01', 'gi1/1/5', 'node-08', 'eth0') - assert result is None + assert C.port.connect_nic( + 'mock-01', 'gi1/1/5', 'node-08', 'eth0' + ) is None def test_port_connect_nic_error(self): C.port.register('mock-01', 'gi1/1/6') @@ -501,8 +461,7 @@ def test_port_connect_nic_error(self): def test_port_detach_nic(self): C.port.register('mock-01', 'gi1/1/7') C.port.connect_nic('mock-01', 'gi1/1/7', 'node-09', 'eth0') - result = C.port.detach_nic('mock-01', 'gi1/1/7') - assert result is None + assert C.port.detach_nic('mock-01', 'gi1/1/7') is None def test_port_detach_nic_error(self): C.port.register('mock-01', 'gi1/1/8') @@ -516,10 +475,8 @@ class Test_user: def test_user_create(self): """ Test user creation. """ - result1 = C.user.create('billy', 'pass1234', 'admin') - result2 = C.user.create('bobby', 'pass1234', 'regular') - assert result1 is None - assert result2 is None + assert C.user.create('billy', 'pass1234', 'admin') is None + assert C.user.create('bobby', 'pass1234', 'regular') is None def test_user_create_duplicate(self): """ Test duplicate user creation. """ @@ -530,8 +487,7 @@ def test_user_create_duplicate(self): def test_user_delete(self): """ Test user deletion. """ C.user.create('jack', 'pass1234', 'admin') - result = C.user.delete('jack') - assert result is None + assert C.user.delete('jack') is None def test_user_delete_error(self): """ Test error condition in user deletion. """ @@ -544,8 +500,7 @@ def test_user_add(self): """ test adding a user to a project. """ C.project.create('proj-sample') C.user.create('Sam', 'pass1234', 'regular') - result = C.user.add('Sam', 'proj-sample') - assert result is None + assert C.user.add('Sam', 'proj-sample') is None def test_user_add_error(self): """Test error condition while granting user access to a project.""" @@ -560,8 +515,7 @@ def test_user_remove(self): C.project.create('test-proj02') C.user.create('sam02', 'pass1234', 'regular') C.user.add('sam02', 'test-proj02') - result = C.user.remove('sam02', 'test-proj02') - assert result is None + assert C.user.remove('sam02', 'test-proj02') is None def test_user_remove_error(self): """Test error condition while revoking user access to a project. """ @@ -580,8 +534,7 @@ class Test_network: def test_network_list(self): """ Test list of networks. """ - result = C.network.list() - assert result == { + assert C.network.list() == { u'net-01': {u'network_id': u'1001', u'projects': [u'proj-01']}, u'net-02': {u'network_id': u'1002', u'projects': [u'proj-01']}, u'net-03': {u'network_id': u'1003', u'projects': [u'proj-01']}, @@ -591,8 +544,7 @@ def test_network_list(self): def test_network_show(self): """ Test show network. """ - result = C.network.show('net-01') - assert result == { + assert C.network.show('net-01') == { u'access': [u'proj-01'], u'channels': [u'vlan/native', u'vlan/1001'], u'name': u'net-01', @@ -601,8 +553,7 @@ def test_network_show(self): def test_network_create(self): """ Test create network. """ - result = C.network.create('net-abcd', 'proj-01', 'proj-01', '') - assert result is None + assert C.network.create('net-abcd', 'proj-01', 'proj-01', '') is None def test_network_create_duplicate(self): """ Test error condition in create network. """ @@ -613,8 +564,7 @@ def test_network_create_duplicate(self): def test_network_delete(self): """ Test network deletion """ C.network.create('net-xyz', 'proj-01', 'proj-01', '') - result = C.network.delete('net-xyz') - assert result is None + assert C.network.delete('net-xyz') is None def test_network_delete_duplicate(self): """ Test error condition in delete network. """ @@ -626,10 +576,8 @@ def test_network_delete_duplicate(self): def test_network_grant_project_access(self): """ Test granting a project access to a network. """ C.network.create('newnet01', 'admin', '', '') - result1 = C.network.grant_access('proj-02', 'newnet01') - result2 = C.network.grant_access('proj-03', 'newnet01') - assert result1 is None - assert result2 is None + assert C.network.grant_access('proj-02', 'newnet01') is None + assert C.network.grant_access('proj-03', 'newnet01') is None def test_network_grant_project_access_error(self): """ Test error while granting a project access to a network. """ @@ -642,8 +590,7 @@ def test_network_revoke_project_access(self): """ Test revoking a project's access to a network. """ C.network.create('newnet02', 'admin', '', '') C.network.grant_access('proj-02', 'newnet02') - result = C.network.revoke_access('proj-02', 'newnet02') - assert result is None + assert C.network.revoke_access('proj-02', 'newnet02') is None def test_network_revoke_project_access_error(self): """ @@ -654,6 +601,3 @@ def test_network_revoke_project_access_error(self): C.network.revoke_access('proj-02', 'newnet03') with pytest.raises(Exception): C.network.revoke_access('proj-02', 'newnet03') - - -# End of tests ## From b01e176daf77e40df37d430a4c1bd167a748ccb0 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 3 Apr 2017 20:50:01 -0400 Subject: [PATCH 63/71] improves tests, removes redundant code --- haas/cli.py | 5 +---- tests/unit/client.py | 48 ++++++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index b22b88f0..5d02a468 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -27,6 +27,7 @@ from functools import wraps from haas.client.client import Client, RequestsHTTPClient, KeystoneHTTPClient +from haas.client.base import FailedAPICallException command_dict = {} @@ -152,10 +153,6 @@ def setup_http_client(): C = Client(ep, http_client) -class FailedAPICallException(Exception): - pass - - def check_clientlib_response(fun): try: return fun() diff --git a/tests/unit/client.py b/tests/unit/client.py index b73ea7d2..28dc1b53 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -28,7 +28,7 @@ from urlparse import urljoin import requests from requests.exceptions import ConnectionError -from haas.client.base import ClientBase +from haas.client.base import ClientBase, FailedAPICallException from haas.client.client import Client, RequestsHTTPClient, KeystoneHTTPClient @@ -48,7 +48,7 @@ class Test_ClientBase: """Tests client initialization and object_url creation. """ def test_init_error(self): - with pytest.raises(Exception): + with pytest.raises(TypeError): x = ClientBase() def test_object_url(self): @@ -293,11 +293,11 @@ def test_node_add_nic(self): def test_node_add_duplicate_nic(self): C.node.remove_nic('node-08', 'eth0') C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.node.add_nic('node-08', 'eth0', 'aa:bb:cc:dd:ee:ff') def test_nosuch_node_add_nic(self): - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.node.add_nic('abcd', 'eth0', 'aa:bb:cc:dd:ee:ff') def test_remove_nic(self): @@ -305,7 +305,7 @@ def test_remove_nic(self): def test_remove_duplicate_nic(self): C.node.remove_nic('node-08', 'eth0') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.node.remove_nic('node-08', 'eth0') def test_node_connect_network(self): @@ -360,7 +360,7 @@ def test_project_create(self): def test_duplicate_project_create(self): """ test for catching duplicate name while creating new project. """ C.project.create('dummy-02') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.project.create('dummy-02') def test_project_delete(self): @@ -370,7 +370,7 @@ def test_project_delete(self): def test_error_project_delete(self): """ test to capture error condition in project delete. """ - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.project.delete('dummy-03') def test_project_connect_node(self): @@ -382,15 +382,15 @@ def test_project_connect_node_duplicate(self): """ test for erronous reconnecting node to project. """ C.project.create('proj-05') C.project.connect('proj-05', 'node-07') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.project.connect('proj-05', 'node-07') def test_project_connect_node_nosuchobject(self): """ test for connecting no such node or project """ C.project.create('proj-06') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.project.connect('proj-06', 'no-such-node') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.project.connect('no-such-project', 'node-06') def test_project_detach_node(self): @@ -402,9 +402,9 @@ def test_project_detach_node(self): def test_project_detach_node_nosuchobject(self): """ Test for while detaching node from project.""" C.project.create('proj-08') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.project.detach('proj-08', 'no-such-node') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.project.detach('no-such-project', 'node-06') @@ -433,7 +433,7 @@ def test_port_register(self): def test_port_dupregister(self): C.port.register('mock-01', 'gi1/1/2') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.port.register('mock-01', 'gi1/1/2') def test_port_delete(self): @@ -443,7 +443,7 @@ def test_port_delete(self): def test_port_delete_error(self): C.port.register('mock-01', 'gi1/1/4') C.port.delete('mock-01', 'gi1/1/4') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.port.delete('mock-01', 'gi1/1/4') def test_port_connect_nic(self): @@ -455,7 +455,7 @@ def test_port_connect_nic(self): def test_port_connect_nic_error(self): C.port.register('mock-01', 'gi1/1/6') C.port.connect_nic('mock-01', 'gi1/1/6', 'node-09', 'eth0') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.port.connect_nic('mock-01', 'gi1/1/6', 'node-08', 'eth0') def test_port_detach_nic(self): @@ -465,7 +465,7 @@ def test_port_detach_nic(self): def test_port_detach_nic_error(self): C.port.register('mock-01', 'gi1/1/8') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.port.detach_nic('mock-01', 'gi1/1/8') @@ -481,7 +481,7 @@ def test_user_create(self): def test_user_create_duplicate(self): """ Test duplicate user creation. """ C.user.create('bill', 'pass1234', 'regular') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.user.create('bill', 'pass1234', 'regular') def test_user_delete(self): @@ -493,7 +493,7 @@ def test_user_delete_error(self): """ Test error condition in user deletion. """ C.user.create('Ata', 'pass1234', 'admin') C.user.delete('Ata') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.user.delete('Ata') def test_user_add(self): @@ -507,7 +507,7 @@ def test_user_add_error(self): C.project.create('test-proj01') C.user.create('sam01', 'pass1234', 'regular') C.user.add('sam01', 'test-proj01') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.user.add('sam01', 'test-proj01') def test_user_remove(self): @@ -524,7 +524,7 @@ def test_user_remove_error(self): C.user.create('xxxx', 'pass1234', 'regular') C.user.add('sam03', 'test-proj03') C.user.remove('sam03', 'test-proj03') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.user.remove('sam03', 'test_proj03') @@ -558,7 +558,7 @@ def test_network_create(self): def test_network_create_duplicate(self): """ Test error condition in create network. """ C.network.create('net-123', 'proj-01', 'proj-01', '') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.network.create('net-123', 'proj-01', 'proj-01', '') def test_network_delete(self): @@ -570,7 +570,7 @@ def test_network_delete_duplicate(self): """ Test error condition in delete network. """ C.network.create('net-xyz', 'proj-01', 'proj-01', '') C.network.delete('net-xyz') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.network.delete('net-xyz') def test_network_grant_project_access(self): @@ -583,7 +583,7 @@ def test_network_grant_project_access_error(self): """ Test error while granting a project access to a network. """ C.network.create('newnet04', 'admin', '', '') C.network.grant_access('proj-02', 'newnet04') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.network.grant_access('proj-02', 'newnet04') def test_network_revoke_project_access(self): @@ -599,5 +599,5 @@ def test_network_revoke_project_access_error(self): C.network.create('newnet03', 'admin', '', '') C.network.grant_access('proj-02', 'newnet03') C.network.revoke_access('proj-02', 'newnet03') - with pytest.raises(Exception): + with pytest.raises(FailedAPICallException): C.network.revoke_access('proj-02', 'newnet03') From cca1c99f94a8eb0b05d3002cd2a9643277e25326 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 4 Apr 2017 19:38:19 -0400 Subject: [PATCH 64/71] fixes a tedious test. --- haas/cli.py | 2 +- haas/client/node.py | 2 +- tests/unit/client.py | 29 ++++++++++++++++------------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/haas/cli.py b/haas/cli.py index 5d02a468..be99016b 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -477,7 +477,7 @@ def headnode_delete_hnic(headnode, nic): @cmd def node_connect_network(node, nic, network, channel): """Connect to on given and """ - check_client_lib( + check_clientlib_response( lambda: C.node.connect_network(node, nic, network, channel) ) diff --git a/haas/client/node.py b/haas/client/node.py index 1d0d924c..482933bb 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -81,7 +81,7 @@ def detach_network(self, node, nic, network): ) payload = json.dumps({'network': network}) return self.check_response( - self.httpClient.request('PUT', url, payload) + self.httpClient.request('POST', url, data=payload) ) def show_console(self, node): diff --git a/tests/unit/client.py b/tests/unit/client.py index 28dc1b53..04dc84ee 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -308,31 +308,34 @@ def test_remove_duplicate_nic(self): with pytest.raises(FailedAPICallException): C.node.remove_nic('node-08', 'eth0') - def test_node_connect_network(self): - assert C.node.connect_network( - 'node-01', 'eth0', 'net-01', 'vlan/native' - ) is None - def test_node_start_console(self): assert C.node.start_console('node-01') is None def test_node_stop_console(self): assert C.node.stop_console('node-01') is None + def test_node_connect_network(self): + assert C.node.connect_network( + 'node-01', 'eth0', 'net-01', 'vlan/native' + ) is None -# FIXME: I spent some time on this test. Looks like the pytest -# framework kills the network server before it can detach network. -# Explanation: The test is unreliable because of following reasons. -# When using a real switch driver for network, haas network server -# times out waiting for response from switch while using mock switch -# driver, the networking_action queue takes longer to execute the request -# the testing server kills the servers when other tests are completed. + def test_node_connect_network_error(self): + C.node.connect_network('node-02', 'eth0', 'net-04', 'vlan/native') + with pytest.raises(FailedAPICallException): + C.node.connect_network('node-02', 'eth0', 'net-04', 'vlan/native') - @pytest.mark.xfail def test_node_detach_network(self): C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') + time.sleep(1) assert C.node.detach_network('node-04', 'eth0', 'net-04') is None + def test_node_detach_network_error(self): + C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') + time.sleep(1) + C.node.detach_network('node-04', 'eth0', 'net-04') + with pytest.raises(FailedAPICallException): + C.node.detach_network('node-04', 'eth0', 'net-04') + @pytest.mark.usefixtures("create_setup") class Test_project: From d6e0247f389fb89225bb427ade07d704ff4f0c67 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Wed, 5 Apr 2017 17:15:28 -0400 Subject: [PATCH 65/71] Updated with comment and increased time to avoid a potential race condition. --- tests/unit/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 04dc84ee..86697714 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -326,12 +326,12 @@ def test_node_connect_network_error(self): def test_node_detach_network(self): C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') - time.sleep(1) + time.sleep(2) # To avoid a potential race condition assert C.node.detach_network('node-04', 'eth0', 'net-04') is None def test_node_detach_network_error(self): C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') - time.sleep(1) + time.sleep(2) # To avoid a potential race condition C.node.detach_network('node-04', 'eth0', 'net-04') with pytest.raises(FailedAPICallException): C.node.detach_network('node-04', 'eth0', 'net-04') From 82a9044aee7b9a53b8b28bb45c65ae810c091be6 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Thu, 6 Apr 2017 11:57:35 -0400 Subject: [PATCH 66/71] solves the race condition for network tests. --- tests/unit/client.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 86697714..0e719f28 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -65,6 +65,8 @@ def test_object_url(self): # 4. Populates haas with dummy objects # 5. tears down the setup in a clean fashion. +db_dir = None + def make_config(): """ This function creates haas.cfg with desired options @@ -72,6 +74,8 @@ def make_config(): It returns a tuple where (tmpdir, cwd) = ('location of haas.cfg', 'pwdd') """ tmpdir = tempfile.mkdtemp() + global db_dir + db_dir = tmpdir cwd = os.getcwd() os.chdir(tmpdir) with open('haas.cfg', 'w') as f: @@ -236,6 +240,21 @@ def populate_server(): ) +# Avoid race condition while creating, deleting networks +def avoid_network_race_condition(): + import sqlite3 + global db_dir # Value of db_dir is populated by `create_setup`. + db_file = db_dir+'/haas.db' + conn = sqlite3.connect(db_file) + cur = conn.cursor() + while True: + que = len(cur.execute('select * from networking_action;').fetchall()) + if que > 0: + time.sleep(1) + else: + break + + @pytest.fixture(scope="module") def create_setup(request): dir_names = make_config() @@ -326,12 +345,12 @@ def test_node_connect_network_error(self): def test_node_detach_network(self): C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') - time.sleep(2) # To avoid a potential race condition + avoid_network_race_condition() assert C.node.detach_network('node-04', 'eth0', 'net-04') is None def test_node_detach_network_error(self): C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') - time.sleep(2) # To avoid a potential race condition + avoid_network_race_condition() C.node.detach_network('node-04', 'eth0', 'net-04') with pytest.raises(FailedAPICallException): C.node.detach_network('node-04', 'eth0', 'net-04') From a1ff5b475c9656f6bec7c4304c08f33e14730c52 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Mon, 10 Apr 2017 08:46:33 -0400 Subject: [PATCH 67/71] uses sqlalchemy to query networkingaction, to avoid race condition in tests --- tests/unit/client.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index 0e719f28..4b966804 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -247,10 +247,41 @@ def avoid_network_race_condition(): db_file = db_dir+'/haas.db' conn = sqlite3.connect(db_file) cur = conn.cursor() + timeout = 0 while True: que = len(cur.execute('select * from networking_action;').fetchall()) if que > 0: time.sleep(1) + timeout = timeout + 1 + if timeout > 10: # breakout if exceeds 10 secs. + break + else: + break + + +def avoid_race_sqlalchemy(): + from haas.model import Node, Project, NetworkingAction + from sqlalchemy import func + from sqlalchemy import create_engine + from sqlalchemy.orm import sessionmaker + + global db_dir # Value of db_dir is populated by `create_setup`. + uri = 'sqlite:///'+db_dir+'/haas.db' + engine = create_engine(uri) + Session = sessionmaker(bind=engine) + session = Session() + timeout = 0 + + while True: + que = session.query( + func.count('*')).select_from( + NetworkingAction + ).scalar() + if que > 0: + time.sleep(1) + timeout = timeout + 1 + if timeout > 10: # breakout if exceeds 10 secs. + break else: break @@ -345,7 +376,7 @@ def test_node_connect_network_error(self): def test_node_detach_network(self): C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') - avoid_network_race_condition() + avoid_race_sqlalchemy() assert C.node.detach_network('node-04', 'eth0', 'net-04') is None def test_node_detach_network_error(self): From 1cd832e0cd79bea467733212e182121d155c8ac5 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Fri, 14 Apr 2017 13:51:34 -0400 Subject: [PATCH 68/71] refactors . Addresses other suggestions by reviewers. --- haas/client/node.py | 17 +++++++-------- tests/unit/client.py | 51 +++++++++++++------------------------------- 2 files changed, 23 insertions(+), 45 deletions(-) diff --git a/haas/client/node.py b/haas/client/node.py index 482933bb..36bbdc6c 100644 --- a/haas/client/node.py +++ b/haas/client/node.py @@ -15,23 +15,22 @@ def list(self, is_free): def show(self, node_name): """Shows attributes of a given node """ - url = self.object_url('node', node_name) return self.check_response(self.httpClient.request('GET', url)) def register(self, node, subtype, *args): """Register a node with appropriate OBM driver. """ -# Registering a node requires apriori knowledge of the -# available OBM driver and its corresponding arguments. -# We assume that the HIL administrator is aware as to which -# Node requires which OBM, and knows arguments required -# for successful node registration. + # Registering a node requires apriori knowledge of the + # available OBM driver and its corresponding arguments. + # We assume that the HIL administrator is aware as to which + # Node requires which OBM, and knows arguments required + # for successful node registration. obm_api = "http://schema.massopencloud.org/haas/v0/obm/" obm_types = ["ipmi", "mock"] -# FIXME: In future obm_types should be dynamically fetched. -# We need a new api call for querying available -# and currently active drivers for HIL + # FIXME: In future obm_types should be dynamically fetched. + # We need a new api call for querying available + # and currently active drivers for HIL raise NotImplementedError def delete(self, node_name): diff --git a/tests/unit/client.py b/tests/unit/client.py index 4b966804..d833662e 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -240,48 +240,27 @@ def populate_server(): ) -# Avoid race condition while creating, deleting networks +# FIX ME: Replace this function with show_networking_action call, once it +# gets implemented. def avoid_network_race_condition(): - import sqlite3 - global db_dir # Value of db_dir is populated by `create_setup`. - db_file = db_dir+'/haas.db' - conn = sqlite3.connect(db_file) - cur = conn.cursor() - timeout = 0 - while True: - que = len(cur.execute('select * from networking_action;').fetchall()) - if que > 0: - time.sleep(1) - timeout = timeout + 1 - if timeout > 10: # breakout if exceeds 10 secs. - break - else: - break + """Checks for networking actions queue to be empty. + Used to avoid race condition between subsequent network calls + on the same object. + """ -def avoid_race_sqlalchemy(): + from flask.ext.sqlalchemy import SQLAlchemy + from haas.flaskapp import app from haas.model import Node, Project, NetworkingAction - from sqlalchemy import func - from sqlalchemy import create_engine - from sqlalchemy.orm import sessionmaker - global db_dir # Value of db_dir is populated by `create_setup`. + global db_dir uri = 'sqlite:///'+db_dir+'/haas.db' - engine = create_engine(uri) - Session = sessionmaker(bind=engine) - session = Session() - timeout = 0 - - while True: - que = session.query( - func.count('*')).select_from( - NetworkingAction - ).scalar() - if que > 0: + db = SQLAlchemy(app) + app.config.update(SQLALCHEMY_DATABASE_URI=uri) + + for timeout in range(10): + if (db.session.query(NetworkingAction).count() > 0): time.sleep(1) - timeout = timeout + 1 - if timeout > 10: # breakout if exceeds 10 secs. - break else: break @@ -376,7 +355,7 @@ def test_node_connect_network_error(self): def test_node_detach_network(self): C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') - avoid_race_sqlalchemy() + avoid_network_race_condition() assert C.node.detach_network('node-04', 'eth0', 'net-04') is None def test_node_detach_network_error(self): From 22800ab0239f2c4986af3159523fca8d4d27cb17 Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Tue, 18 Apr 2017 07:19:57 -0400 Subject: [PATCH 69/71] Improves tests, provides explicit message for time out. Moves imports to the top of test file. corrects a typo in comment. --- tests/unit/client.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index d833662e..e2f0f72e 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -28,10 +28,12 @@ from urlparse import urljoin import requests from requests.exceptions import ConnectionError +from flask.ext.sqlalchemy import SQLAlchemy +from haas.flaskapp import app +from haas.model import NetworkingAction from haas.client.base import ClientBase, FailedAPICallException from haas.client.client import Client, RequestsHTTPClient, KeystoneHTTPClient - ep = "http://127.0.0.1:8000" or os.environ.get('HAAS_ENDPOINT') username = "hil_user" or os.environ.get('HAAS_USERNAME') password = "hil_pass1234" or os.environ.get('HAAS_PASSWORD') @@ -71,7 +73,7 @@ def test_object_url(self): def make_config(): """ This function creates haas.cfg with desired options and writes to a temporary directory. - It returns a tuple where (tmpdir, cwd) = ('location of haas.cfg', 'pwdd') + It returns a tuple where (tmpdir, cwd) = ('location of haas.cfg', 'pwd') """ tmpdir = tempfile.mkdtemp() global db_dir @@ -248,21 +250,18 @@ def avoid_network_race_condition(): Used to avoid race condition between subsequent network calls on the same object. """ - - from flask.ext.sqlalchemy import SQLAlchemy - from haas.flaskapp import app - from haas.model import Node, Project, NetworkingAction - global db_dir uri = 'sqlite:///'+db_dir+'/haas.db' db = SQLAlchemy(app) app.config.update(SQLALCHEMY_DATABASE_URI=uri) for timeout in range(10): - if (db.session.query(NetworkingAction).count() > 0): + que = db.session.query(NetworkingAction).count() + if (que > 0): time.sleep(1) else: - break + return que + return timeout @pytest.fixture(scope="module") @@ -355,15 +354,21 @@ def test_node_connect_network_error(self): def test_node_detach_network(self): C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') - avoid_network_race_condition() - assert C.node.detach_network('node-04', 'eth0', 'net-04') is None + x = avoid_network_race_condition() + if x == 0: + assert C.node.detach_network('node-04', 'eth0', 'net-04') is None + else: + assert False, "Timing out a race condition for networking actions." def test_node_detach_network_error(self): C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') - avoid_network_race_condition() - C.node.detach_network('node-04', 'eth0', 'net-04') - with pytest.raises(FailedAPICallException): + x = avoid_network_race_condition() + if x == 0: C.node.detach_network('node-04', 'eth0', 'net-04') + with pytest.raises(FailedAPICallException): + C.node.detach_network('node-04', 'eth0', 'net-04') + else: + assert False, "Timing out a race condition for networking actions." @pytest.mark.usefixtures("create_setup") From 157e518753b6ca5ad7b77b807b56500446deb45f Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Thu, 20 Apr 2017 11:34:06 -0400 Subject: [PATCH 70/71] minorly improves --- tests/unit/client.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/tests/unit/client.py b/tests/unit/client.py index e2f0f72e..5cc28473 100644 --- a/tests/unit/client.py +++ b/tests/unit/client.py @@ -242,6 +242,10 @@ def populate_server(): ) +class TimeoutError(Exception): + pass + + # FIX ME: Replace this function with show_networking_action call, once it # gets implemented. def avoid_network_race_condition(): @@ -260,8 +264,8 @@ def avoid_network_race_condition(): if (que > 0): time.sleep(1) else: - return que - return timeout + return + raise TimeoutError(" Timed out to avoid race condition. ") @pytest.fixture(scope="module") @@ -354,21 +358,15 @@ def test_node_connect_network_error(self): def test_node_detach_network(self): C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') - x = avoid_network_race_condition() - if x == 0: - assert C.node.detach_network('node-04', 'eth0', 'net-04') is None - else: - assert False, "Timing out a race condition for networking actions." + avoid_network_race_condition() + assert C.node.detach_network('node-04', 'eth0', 'net-04') is None def test_node_detach_network_error(self): C.node.connect_network('node-04', 'eth0', 'net-04', 'vlan/native') - x = avoid_network_race_condition() - if x == 0: + avoid_network_race_condition() + C.node.detach_network('node-04', 'eth0', 'net-04') + with pytest.raises(FailedAPICallException): C.node.detach_network('node-04', 'eth0', 'net-04') - with pytest.raises(FailedAPICallException): - C.node.detach_network('node-04', 'eth0', 'net-04') - else: - assert False, "Timing out a race condition for networking actions." @pytest.mark.usefixtures("create_setup") From 922437f3b2ca6dbac9c53a4392b47690a09bf8ad Mon Sep 17 00:00:00 2001 From: Sahil Tikale Date: Fri, 21 Apr 2017 16:42:15 -0400 Subject: [PATCH 71/71] Catches error of not setting HAAS_ENDPOINT. --- haas/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/haas/cli.py b/haas/cli.py index be99016b..b59568c5 100644 --- a/haas/cli.py +++ b/haas/cli.py @@ -114,7 +114,10 @@ def setup_http_client(): global http_client global C # initiating the client library # First try basic auth: - ep = os.environ.get('HAAS_ENDPOINT') or "http://127.0.0.1:5000" + ep = ( + os.environ.get('HAAS_ENDPOINT') or + sys.stdout.write("Error: HAAS_ENDPOINT not set \n") + ) basic_username = os.getenv('HAAS_USERNAME') basic_password = os.getenv('HAAS_PASSWORD') if basic_username is not None and basic_password is not None: