-
Notifications
You must be signed in to change notification settings - Fork 54
Client Library HIL | query calls #617
Changes from 82 commits
e75786c
61bb87d
3d8ca6d
ee5aa40
529b37c
e5cdad3
060a55e
5237d61
dc4ffea
cae9dd4
06b197a
f9eb580
2e63285
f36ac1f
ddf43d4
595c30d
b0106ac
27a619f
e04d629
ae7c278
7cddc95
ef94c61
e5f3150
c07cc78
16de104
404a37b
c3d54bf
16bb93c
da671fc
2e164cd
e132ea6
485ced3
b9ffe29
ba23ded
174cec7
347090c
bcbcd12
37cf4b0
4d50af5
21f9d13
559bc40
d65ed0f
16bbefd
bac7d39
5c29a76
98967cd
0556f3d
14cedef
7b6523e
7fe634d
0213717
d1c9eba
e22c210
5e30397
b45ec80
d20c55f
adc94d0
493ae42
290e150
a54ce5e
b1b3e97
de9ddeb
d266f4d
a8a0a71
df915fd
e05786f
3b1ece8
8a33ed0
6a8b932
42bd4dd
c7195a1
67ba238
f48e608
696f3ef
5786e37
b01e176
cca1c99
d6e0247
82a9044
a1ff5b4
1cd832e
22800ab
157e518
922437f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
""" This module implements the HaaS client library. """ | ||
|
||
import requests | ||
import os | ||
import json | ||
from urlparse import urljoin | ||
|
||
|
||
class FailedAPICallException(Exception): | ||
pass | ||
|
||
|
||
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, httpClient): | ||
""" 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. | ||
""" | ||
self.endpoint = endpoint | ||
self.httpClient = httpClient | ||
|
||
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 | ||
|
||
def check_response(self, response): | ||
if response.ok: | ||
if response.request.method == 'GET': | ||
return response.json() | ||
else: # For methods PUT, POST, DELETE | ||
return | ||
else: | ||
e = response.json() | ||
raise FailedAPICallException(e['msg']) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
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.switch import Port | ||
from haas.client.network import Network | ||
from haas.client.user import User | ||
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, httpClient): | ||
self.httpClient = httpClient | ||
self.endpoint = endpoint | ||
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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import json | ||
from haas.client.base import ClientBase | ||
|
||
|
||
class Network(ClientBase): | ||
"""Consists of calls to query and manipulate network related | ||
|
||
objects and relations. | ||
""" | ||
|
||
def list(self): | ||
"""Lists all projects under HIL """ | ||
url = self.object_url('networks') | ||
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.httpClient.request("GET", url)) | ||
|
||
def create(self, network, owner, access, net_id): | ||
"""Create a link-layer <network>. | ||
|
||
See docs/networks.md for details. | ||
""" | ||
url = self.object_url('network', network) | ||
payload = json.dumps({ | ||
'owner': owner, 'access': access, | ||
'net_id': net_id | ||
}) | ||
return self.check_response( | ||
self.httpClient.request("PUT", url, data=payload) | ||
) | ||
|
||
def delete(self, network): | ||
"""Delete a <network>. """ | ||
url = self.object_url('network', network) | ||
return self.check_response(self.httpClient.request("DELETE", url)) | ||
|
||
def grant_access(self, project, network): | ||
"""Grants <project> access to <network>. """ | ||
url = self.object_url( | ||
'network', network, 'access', project | ||
) | ||
return self.check_response(self.httpClient.request("PUT", url)) | ||
|
||
def revoke_access(self, project, network): | ||
"""Removes access of <network> from <project>. """ | ||
url = self.object_url( | ||
'network', network, 'access', project | ||
) | ||
return self.check_response(self.httpClient.request("DELETE", url)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import json | ||
from haas.client.base import ClientBase | ||
|
||
|
||
class Node(ClientBase): | ||
"""Consists of calls to query and manipulate node related | ||
|
||
objects and relations. | ||
""" | ||
|
||
def list(self, is_free): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should is_free have a default (like maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I checked the cli, client and api code for this call. CLI rejects any other options other than |
||
"""List all nodes that HIL manages """ | ||
url = self.object_url('nodes', is_free) | ||
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.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. | ||
|
||
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 | ||
raise NotImplementedError | ||
|
||
def delete(self, node_name): | ||
"""Deletes the node from database. """ | ||
url = self.object_url('node', node_name) | ||
return check_response(self.httpClient.request('DELETE', url)) | ||
|
||
def power_cycle(self, node_name): | ||
"""Power cycles the <node> """ | ||
url = self.object_url('node', node_name, 'power_cycle') | ||
return self.check_response(self.httpClient.request('POST', url)) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. blank lines There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed it. |
||
def power_off(self, node_name): | ||
"""Power offs the <node> """ | ||
url = self.object_url('node', node_name, 'power_off') | ||
return self.check_response(self.httpClient.request('POST', url)) | ||
|
||
def add_nic(self, node_name, nic_name, macaddr): | ||
"""Add a <nic> to <node>""" | ||
url = self.object_url('node', node_name, 'nic', nic_name) | ||
payload = json.dumps({'macaddr': macaddr}) | ||
return self.check_response( | ||
self.httpClient.request('PUT', url, data=payload) | ||
) | ||
|
||
def remove_nic(self, node_name, nic_name): | ||
"""Remove a <nic> from <node>""" | ||
url = self.object_url('node', node_name, 'nic', nic_name) | ||
return self.check_response(self.httpClient.request('DELETE', url)) | ||
|
||
def connect_network(self, node, nic, network, channel): | ||
"""Connect <node> to <network> on given <nic> and <channel>""" | ||
url = self.object_url( | ||
'node', node, 'nic', nic, 'connect_network' | ||
) | ||
payload = json.dumps({ | ||
'network': network, 'channel': channel | ||
}) | ||
return self.check_response( | ||
self.httpClient.request('POST', url, data=payload) | ||
) | ||
|
||
def detach_network(self, node, nic, network): | ||
"""Disconnect <node> from <network> on the given <nic>. """ | ||
url = self.object_url( | ||
'node', node, 'nic', nic, 'detach_network' | ||
) | ||
payload = json.dumps({'network': network}) | ||
return self.check_response( | ||
self.httpClient.request('POST', url, data=payload) | ||
) | ||
|
||
def show_console(self, node): | ||
"""Display console log for <node> """ | ||
raise NotImplementedError | ||
|
||
def start_console(self, node): | ||
"""Start logging console output from <node> """ | ||
url = self.object_url('node', node, 'console') | ||
return self.check_response(self.httpClient.request('PUT', url)) | ||
|
||
def stop_console(self, node): | ||
"""Stop logging console output from <node> and delete the log""" | ||
url = self.object_url('node', node, 'console') | ||
return self.check_response(self.httpClient.request('DELETE', url)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import json | ||
from haas.client.base import ClientBase | ||
|
||
|
||
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') | ||
return self.check_response(self.httpClient.request("GET", url)) | ||
|
||
def nodes_in(self, project_name): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we rename this and the next one to nodes() and networks() ? |
||
"""Lists nodes allocated to project <project_name> """ | ||
url = self.object_url('project', project_name, 'nodes') | ||
return self.check_response(self.httpClient.request("GET", url)) | ||
|
||
def networks_in(self, project_name): | ||
"""Lists nodes allocated to project <project_name> """ | ||
url = self.object_url( | ||
'project', project_name, 'networks' | ||
) | ||
return self.check_response(self.httpClient.request("GET", url)) | ||
|
||
def create(self, project_name): | ||
"""Creates a project named <project_name> """ | ||
url = self.object_url('project', project_name) | ||
return self.check_response(self.httpClient.request("PUT", url)) | ||
|
||
def delete(self, project_name): | ||
"""Deletes a project named <project_name> """ | ||
url = self.object_url('project', project_name) | ||
return self.check_response(self.httpClient.request("DELETE", url)) | ||
|
||
def connect(self, project_name, node_name): | ||
"""Adds a node to a project. """ | ||
url = self.object_url( | ||
'project', project_name, 'connect_node' | ||
) | ||
self.payload = json.dumps({'node': node_name}) | ||
return self.check_response( | ||
self.httpClient.request("POST", url, data=self.payload) | ||
) | ||
|
||
def detach(self, project_name, node_name): | ||
"""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( | ||
self.httpClient.request("POST", url, data=self.payload) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blank lines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.