add synergy CLI
Change-Id: Ibd60ee3f448873e381126ff35a635e84971ab137
This commit is contained in:
parent
31d50774e7
commit
a56f18470f
@ -27,6 +27,12 @@ scripts =
|
|||||||
synergy.managers =
|
synergy.managers =
|
||||||
timer = synergy.examples.timer_manager:TimerManager
|
timer = synergy.examples.timer_manager:TimerManager
|
||||||
|
|
||||||
|
synergy.commands =
|
||||||
|
list = synergy.client.command:LIST
|
||||||
|
status = synergy.client.command:STATUS
|
||||||
|
start = synergy.client.command:START
|
||||||
|
stop = synergy.client.command:STOP
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
build-dir = doc/build
|
build-dir = doc/build
|
||||||
|
0
synergy/client/__init__.py
Normal file
0
synergy/client/__init__.py
Normal file
258
synergy/client/command.py
Normal file
258
synergy/client/command.py
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
__author__ = "Lisa Zangrando"
|
||||||
|
__email__ = "lisa.zangrando[AT]pd.infn.it"
|
||||||
|
__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud
|
||||||
|
All Rights Reserved
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0;
|
||||||
|
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."""
|
||||||
|
|
||||||
|
"""
|
||||||
|
class BaseCommand(object):
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.parameters = {}
|
||||||
|
self.results = {}
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def getParameters(self):
|
||||||
|
return self.parameters
|
||||||
|
|
||||||
|
def addParameter(self, name, value):
|
||||||
|
self.parameters[name] = value
|
||||||
|
|
||||||
|
def getParameter(self, name):
|
||||||
|
return self.parameters.get(name, None)
|
||||||
|
|
||||||
|
def getResults(self):
|
||||||
|
return self.results
|
||||||
|
|
||||||
|
def addResult(self, name, value):
|
||||||
|
self.results[name] = value
|
||||||
|
|
||||||
|
def getResult(self, name):
|
||||||
|
return self.getResults().get(name, None)
|
||||||
|
|
||||||
|
def setResults(self, data):
|
||||||
|
self.results = data
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPCommand(BaseCommand):
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPCommand(object):
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
# def __init__(self, name):
|
||||||
|
# super(HTTPCommand, self).__init__(name)
|
||||||
|
|
||||||
|
def configureParser(self, subparser):
|
||||||
|
raise NotImplementedError("not implemented!")
|
||||||
|
|
||||||
|
def log(self):
|
||||||
|
raise NotImplementedError("not implemented!")
|
||||||
|
|
||||||
|
def sendRequest(self, synergy_url, payload=None):
|
||||||
|
request = requests.get(synergy_url, params=payload)
|
||||||
|
|
||||||
|
if request.status_code != requests.codes.ok:
|
||||||
|
# print(request.reason)
|
||||||
|
# print(request.status_code)
|
||||||
|
request.raise_for_status()
|
||||||
|
|
||||||
|
self.results = request.json()
|
||||||
|
# self.setResults(request.json())
|
||||||
|
return request
|
||||||
|
|
||||||
|
def getResults(self):
|
||||||
|
return self.results
|
||||||
|
|
||||||
|
|
||||||
|
class LIST(HTTPCommand):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(LIST, self).__init__("list")
|
||||||
|
|
||||||
|
def configureParser(self, subparser):
|
||||||
|
subparser.add_parser("list", add_help=True, help="list the managers")
|
||||||
|
|
||||||
|
def sendRequest(self, synergy_url, args=None):
|
||||||
|
super(LIST, self).sendRequest(synergy_url + "/synergy/list")
|
||||||
|
|
||||||
|
def log(self):
|
||||||
|
results = self.getResults()
|
||||||
|
|
||||||
|
max_project_id = max(len(max(results, key=len)), len("manager"))
|
||||||
|
separator_str = "-" * (max_project_id + 4) + "\n"
|
||||||
|
format_str = "| {0:%ss} |\n" % (max_project_id)
|
||||||
|
|
||||||
|
msg = separator_str
|
||||||
|
msg += format_str.format("manager")
|
||||||
|
msg += separator_str
|
||||||
|
|
||||||
|
for manager in results:
|
||||||
|
msg += format_str.format(manager)
|
||||||
|
|
||||||
|
msg += separator_str
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class START(HTTPCommand):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(START, self).__init__("start")
|
||||||
|
|
||||||
|
def configureParser(self, subparser):
|
||||||
|
parser = subparser.add_parser("start",
|
||||||
|
add_help=True,
|
||||||
|
help="start the managers")
|
||||||
|
|
||||||
|
parser.add_argument("manager", help="the manager to be started")
|
||||||
|
|
||||||
|
def sendRequest(self, synergy_url, args):
|
||||||
|
super(START, self).sendRequest(synergy_url + "/synergy/start",
|
||||||
|
{"manager": args.manager})
|
||||||
|
|
||||||
|
def log(self):
|
||||||
|
results = self.getResults()
|
||||||
|
|
||||||
|
max_manager = max(len(max(results.keys(), key=len)), len("manager"))
|
||||||
|
|
||||||
|
max_status = len("status")
|
||||||
|
max_msg = len("message")
|
||||||
|
|
||||||
|
for result in results.values():
|
||||||
|
max_status = max(len(str(result["status"])), max_status)
|
||||||
|
max_msg = max(len(str(result["message"])), max_msg)
|
||||||
|
|
||||||
|
separator_str = "-" * (max_manager + max_status + max_msg + 10) + "\n"
|
||||||
|
|
||||||
|
format_str = "| {0:%ss} | {1:%ss} | {2:%ss} |\n" % (max_manager,
|
||||||
|
max_status,
|
||||||
|
max_msg)
|
||||||
|
|
||||||
|
msg = separator_str
|
||||||
|
msg += format_str.format("manager", "status", "message")
|
||||||
|
msg += separator_str
|
||||||
|
|
||||||
|
for manager, values in results.items():
|
||||||
|
msg += format_str.format(manager,
|
||||||
|
values["status"],
|
||||||
|
values["message"])
|
||||||
|
|
||||||
|
msg += separator_str
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class STOP(HTTPCommand):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(STOP, self).__init__("stop")
|
||||||
|
|
||||||
|
def configureParser(self, subparser):
|
||||||
|
parser = subparser.add_parser("stop",
|
||||||
|
add_help=True,
|
||||||
|
help="stop the managers")
|
||||||
|
|
||||||
|
parser.add_argument("manager", help="the manager to be stopped")
|
||||||
|
|
||||||
|
def sendRequest(self, synergy_url, args):
|
||||||
|
super(STOP, self).sendRequest(synergy_url + "/synergy/stop",
|
||||||
|
{"manager": args.manager})
|
||||||
|
|
||||||
|
def log(self):
|
||||||
|
results = self.getResults()
|
||||||
|
|
||||||
|
max_manager = max(len(max(results.keys(), key=len)), len("manager"))
|
||||||
|
max_status = len("status")
|
||||||
|
max_msg = len("message")
|
||||||
|
|
||||||
|
for result in results.values():
|
||||||
|
max_status = max(len(str(result["status"])), max_status)
|
||||||
|
max_msg = max(len(str(result["message"])), max_msg)
|
||||||
|
|
||||||
|
separator_str = "-" * (max_manager + max_status + max_msg + 10) + "\n"
|
||||||
|
format_str = "| {0:%ss} | {1:%ss} | {2:%ss} |\n" % (max_manager,
|
||||||
|
max_status,
|
||||||
|
max_msg)
|
||||||
|
|
||||||
|
msg = separator_str
|
||||||
|
msg += format_str.format("manager", "status", "message")
|
||||||
|
msg += separator_str
|
||||||
|
|
||||||
|
for manager, values in results.items():
|
||||||
|
msg += format_str.format(manager,
|
||||||
|
values["status"],
|
||||||
|
values["message"])
|
||||||
|
|
||||||
|
msg += separator_str
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class STATUS(HTTPCommand):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(STATUS, self).__init__("status")
|
||||||
|
|
||||||
|
def configureParser(self, subparser):
|
||||||
|
parser = subparser.add_parser("status",
|
||||||
|
add_help=True,
|
||||||
|
help="retrieve the manager's status")
|
||||||
|
|
||||||
|
parser.add_argument("manager", nargs='*', help="the managers list")
|
||||||
|
|
||||||
|
def sendRequest(self, synergy_url, args):
|
||||||
|
super(STATUS, self).sendRequest(synergy_url + "/synergy/status",
|
||||||
|
{"manager": args.manager})
|
||||||
|
|
||||||
|
def log(self):
|
||||||
|
results = self.getResults()
|
||||||
|
|
||||||
|
max_project_id = max(len(max(results.keys(), key=len)), len("manager"))
|
||||||
|
max_value = max(len(max(results.values(), key=len)), len("status"))
|
||||||
|
separator_str = "-" * (max_project_id + max_value + 7) + "\n"
|
||||||
|
format_str = "| {0:%ss} | {1:%ss} |\n" % (max_project_id, max_value)
|
||||||
|
|
||||||
|
msg = separator_str
|
||||||
|
msg += format_str.format("manager", "status")
|
||||||
|
msg += separator_str
|
||||||
|
|
||||||
|
for manager, status in results.items():
|
||||||
|
msg += format_str.format(manager, status)
|
||||||
|
|
||||||
|
msg += separator_str
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class EXECUTE(HTTPCommand):
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
super(EXECUTE, self).__init__(name)
|
||||||
|
|
||||||
|
def sendRequest(self, synergy_url, manager, command, args=None):
|
||||||
|
payload = {"manager": manager,
|
||||||
|
"command": command,
|
||||||
|
"args": args}
|
||||||
|
|
||||||
|
super(EXECUTE, self).sendRequest(synergy_url + "/synergy/execute",
|
||||||
|
payload)
|
589
synergy/client/keystone_v3.py
Normal file
589
synergy/client/keystone_v3.py
Normal file
@ -0,0 +1,589 @@
|
|||||||
|
import json
|
||||||
|
import os.path
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
__author__ = "Lisa Zangrando"
|
||||||
|
__email__ = "lisa.zangrando[AT]pd.infn.it"
|
||||||
|
__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud
|
||||||
|
All Rights Reserved
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0;
|
||||||
|
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."""
|
||||||
|
|
||||||
|
|
||||||
|
class Trust(object):
|
||||||
|
|
||||||
|
def __init__(self, data):
|
||||||
|
data = data["trust"]
|
||||||
|
|
||||||
|
self.id = data["id"]
|
||||||
|
self.impersonations = data["impersonation"]
|
||||||
|
self.roles_links = data["roles_links"]
|
||||||
|
self.trustor_user_id = data["trustor_user_id"]
|
||||||
|
self.trustee_user_id = data["trustee_user_id"]
|
||||||
|
self.links = data["links"]
|
||||||
|
self.roles = data["roles"]
|
||||||
|
self.remaining_uses = data["remaining_uses"]
|
||||||
|
self.expires_at = None
|
||||||
|
|
||||||
|
if data["expires_at"] is not None:
|
||||||
|
self.expires_at = datetime.strptime(data["expires_at"],
|
||||||
|
"%Y-%m-%dT%H:%M:%S.%fZ")
|
||||||
|
self.project_id = data["project_id"]
|
||||||
|
|
||||||
|
def getId(self):
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
def isImpersonations(self):
|
||||||
|
return self.impersonations
|
||||||
|
|
||||||
|
def getRolesLinks(self):
|
||||||
|
return self.roles_links
|
||||||
|
|
||||||
|
def getTrustorUserId(self):
|
||||||
|
return self.trustor_user_id
|
||||||
|
|
||||||
|
def getTrusteeUserId(self):
|
||||||
|
return self.trustee_user_id
|
||||||
|
|
||||||
|
def getlinks(self):
|
||||||
|
return self.links
|
||||||
|
|
||||||
|
def getProjectId(self):
|
||||||
|
return self.project_id
|
||||||
|
|
||||||
|
def getRoles(self):
|
||||||
|
return self.roles
|
||||||
|
|
||||||
|
def getRemainingUses(self):
|
||||||
|
return self.remaining_uses
|
||||||
|
|
||||||
|
def getExpiration(self):
|
||||||
|
return self.expires_at
|
||||||
|
|
||||||
|
def isExpired(self):
|
||||||
|
if self.getExpiration() is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.getExpiration() < datetime.utcnow()
|
||||||
|
|
||||||
|
|
||||||
|
class Token(object):
|
||||||
|
|
||||||
|
def __init__(self, token, data):
|
||||||
|
self.id = token
|
||||||
|
|
||||||
|
data = data["token"]
|
||||||
|
self.roles = data["roles"]
|
||||||
|
self.catalog = data["catalog"]
|
||||||
|
self.issued_at = datetime.strptime(data["issued_at"],
|
||||||
|
"%Y-%m-%dT%H:%M:%S.%fZ")
|
||||||
|
self.expires_at = datetime.strptime(data["expires_at"],
|
||||||
|
"%Y-%m-%dT%H:%M:%S.%fZ")
|
||||||
|
self.project = data["project"]
|
||||||
|
self.user = data["user"]
|
||||||
|
self.extras = data["extras"]
|
||||||
|
|
||||||
|
def getCatalog(self, service_name=None, interface="public"):
|
||||||
|
if service_name:
|
||||||
|
for service in self.catalog:
|
||||||
|
if service["name"] == service_name:
|
||||||
|
for endpoint in service["endpoints"]:
|
||||||
|
if endpoint["interface"] == interface:
|
||||||
|
return endpoint
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self.catalog
|
||||||
|
|
||||||
|
def getExpiration(self):
|
||||||
|
return self.expires_at
|
||||||
|
|
||||||
|
def getId(self):
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
def getExtras(self):
|
||||||
|
return self.extras
|
||||||
|
|
||||||
|
def getProject(self):
|
||||||
|
return self.project
|
||||||
|
|
||||||
|
def getRoles(self):
|
||||||
|
return self.roles
|
||||||
|
|
||||||
|
def getUser(self):
|
||||||
|
return self.user
|
||||||
|
|
||||||
|
def isAdmin(self):
|
||||||
|
if not self.roles:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for role in self.roles:
|
||||||
|
if role["name"] == "admin":
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def issuedAt(self):
|
||||||
|
return self.issued_at
|
||||||
|
|
||||||
|
def isExpired(self):
|
||||||
|
return self.getExpiration() < datetime.utcnow()
|
||||||
|
|
||||||
|
def save(self, filename):
|
||||||
|
# save to file
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
token = {}
|
||||||
|
token["catalog"] = self.catalog
|
||||||
|
token["extras"] = self.extras
|
||||||
|
token["user"] = self.user
|
||||||
|
token["project"] = self.project
|
||||||
|
token["roles"] = self.roles
|
||||||
|
token["roles"] = self.roles
|
||||||
|
token["issued_at"] = self.issued_at.isoformat()
|
||||||
|
token["expires_at"] = self.expires_at.isoformat()
|
||||||
|
|
||||||
|
data = {"id": self.id, "token": token}
|
||||||
|
|
||||||
|
json.dump(data, f)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, filename):
|
||||||
|
if not os.path.isfile(".auth_token"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# load from file:
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
try:
|
||||||
|
data = json.load(f)
|
||||||
|
return Token(data["id"], data)
|
||||||
|
# if the file is empty the ValueError will be thrown
|
||||||
|
except ValueError as ex:
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
def isotime(self, at=None, subsecond=False):
|
||||||
|
"""Stringify time in ISO 8601 format."""
|
||||||
|
if not at:
|
||||||
|
at = datetime.utcnow()
|
||||||
|
|
||||||
|
if not subsecond:
|
||||||
|
st = at.strftime('%Y-%m-%dT%H:%M:%S')
|
||||||
|
else:
|
||||||
|
st = at.strftime('%Y-%m-%dT%H:%M:%S.%f')
|
||||||
|
|
||||||
|
if at.tzinfo:
|
||||||
|
tz = at.tzinfo.tzname(None)
|
||||||
|
else:
|
||||||
|
tz = 'UTC'
|
||||||
|
|
||||||
|
st += ('Z' if tz == 'UTC' else tz)
|
||||||
|
return st
|
||||||
|
|
||||||
|
"""The trustor or grantor of a trust is the person who creates the trust.
|
||||||
|
The trustor is the one who contributes property to the trust.
|
||||||
|
The trustee is the person who manages the trust, and is usually appointed
|
||||||
|
by the trustor. The trustor is also often the trustee in living trusts.
|
||||||
|
"""
|
||||||
|
def trust(self, trustee_user, expires_at=None,
|
||||||
|
project_id=None, roles=None, impersonation=True):
|
||||||
|
if self.isExpired():
|
||||||
|
raise Exception("token expired!")
|
||||||
|
|
||||||
|
headers = {"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": "python-novaclient",
|
||||||
|
"X-Auth-Token": self.getId()}
|
||||||
|
|
||||||
|
if roles is None:
|
||||||
|
roles = self.getRoles()
|
||||||
|
|
||||||
|
if project_id is None:
|
||||||
|
project_id = self.getProject().get("id")
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
data["trust"] = {"impersonation": impersonation,
|
||||||
|
"project_id": project_id,
|
||||||
|
"roles": roles,
|
||||||
|
"trustee_user_id": trustee_user,
|
||||||
|
"trustor_user_id": self.getUser().get("id")}
|
||||||
|
|
||||||
|
if expires_at is not None:
|
||||||
|
data["trust"]["expires_at"] = self.isotime(expires_at, True)
|
||||||
|
|
||||||
|
endpoint = self.getCatalog(service_name="keystone")
|
||||||
|
|
||||||
|
if not endpoint:
|
||||||
|
raise Exception("keystone endpoint not found!")
|
||||||
|
|
||||||
|
if "v2.0" in endpoint["url"]:
|
||||||
|
endpoint["url"] = endpoint["url"].replace("v2.0", "v3")
|
||||||
|
|
||||||
|
response = requests.post(url=endpoint["url"] + "/OS-TRUST/trusts",
|
||||||
|
headers=headers,
|
||||||
|
data=json.dumps(data))
|
||||||
|
|
||||||
|
if response.status_code != requests.codes.ok:
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
if not response.text:
|
||||||
|
raise Exception("trust token failed!")
|
||||||
|
|
||||||
|
return Trust(response.json())
|
||||||
|
|
||||||
|
|
||||||
|
class KeystoneClient(object):
|
||||||
|
|
||||||
|
def __init__(self, auth_url, username, password, project_id=None,
|
||||||
|
project_name=None, timeout=None,
|
||||||
|
default_trust_expiration=None):
|
||||||
|
self.auth_url = auth_url
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.project_id = project_id
|
||||||
|
self.project_name = project_name
|
||||||
|
self.timeout = timeout
|
||||||
|
self.token = None
|
||||||
|
|
||||||
|
if default_trust_expiration:
|
||||||
|
self.default_trust_expiration = default_trust_expiration
|
||||||
|
else:
|
||||||
|
self.default_trust_expiration = 24
|
||||||
|
|
||||||
|
def authenticate(self):
|
||||||
|
if self.token is not None:
|
||||||
|
if self.token.isExpired():
|
||||||
|
try:
|
||||||
|
self.deleteToken(self.token.getId())
|
||||||
|
except requests.exceptions.HTTPError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
headers = {"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": "python-novaclient"}
|
||||||
|
|
||||||
|
identity = {"methods": ["password"],
|
||||||
|
"password": {"user": {"name": self.username,
|
||||||
|
"domain": {"id": "default"},
|
||||||
|
"password": self.password}}}
|
||||||
|
|
||||||
|
data = {"auth": {}}
|
||||||
|
data["auth"]["identity"] = identity
|
||||||
|
|
||||||
|
if self.project_name:
|
||||||
|
data["auth"]["scope"] = {"project": {"name": self.project_name,
|
||||||
|
"domain": {"id": "default"}}}
|
||||||
|
|
||||||
|
if self.project_id:
|
||||||
|
data["auth"]["scope"] = {"project": {"id": self.project_id,
|
||||||
|
"domain": {"id": "default"}}}
|
||||||
|
|
||||||
|
response = requests.post(url=self.auth_url + "/auth/tokens",
|
||||||
|
headers=headers,
|
||||||
|
data=json.dumps(data),
|
||||||
|
timeout=self.timeout)
|
||||||
|
|
||||||
|
if response.status_code != requests.codes.ok:
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
if not response.text:
|
||||||
|
raise Exception("authentication failed!")
|
||||||
|
|
||||||
|
# print(response.__dict__)
|
||||||
|
|
||||||
|
token_subject = response.headers["X-Subject-Token"]
|
||||||
|
token_data = response.json()
|
||||||
|
|
||||||
|
self.token = Token(token_subject, token_data)
|
||||||
|
|
||||||
|
def getUser(self, id):
|
||||||
|
try:
|
||||||
|
response = self.getResource("users/%s" % id, "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the user info (id=%r): %s"
|
||||||
|
% (id, response["error"]["message"]))
|
||||||
|
|
||||||
|
if response:
|
||||||
|
response = response["user"]
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def getUsers(self):
|
||||||
|
try:
|
||||||
|
response = self.getResource("users", "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the users list: %s"
|
||||||
|
% response["error"]["message"])
|
||||||
|
|
||||||
|
if response:
|
||||||
|
response = response["users"]
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def getUserProjects(self, id):
|
||||||
|
try:
|
||||||
|
response = self.getResource("users/%s/projects" % id, "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the users's projects "
|
||||||
|
"(id=%r): %s" % (id, response["error"]["message"]))
|
||||||
|
|
||||||
|
if response:
|
||||||
|
response = response["projects"]
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def getUserRoles(self, user_id, project_id):
|
||||||
|
try:
|
||||||
|
response = self.getResource("/projects/%s/users/%s/roles"
|
||||||
|
% (project_id, user_id), "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the user's roles (usrId=%r, "
|
||||||
|
"prjId=%r): %s" % (user_id,
|
||||||
|
project_id,
|
||||||
|
response["error"]["message"]))
|
||||||
|
|
||||||
|
if response:
|
||||||
|
response = response["roles"]
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def getProject(self, id):
|
||||||
|
try:
|
||||||
|
response = self.getResource("/projects/%s" % id, "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the project (id=%r, "
|
||||||
|
% (id, response["error"]["message"]))
|
||||||
|
|
||||||
|
if response:
|
||||||
|
response = response["project"]
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def getProjects(self):
|
||||||
|
try:
|
||||||
|
response = self.getResource("/projects", "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the projects list: %s"
|
||||||
|
% response["error"]["message"])
|
||||||
|
|
||||||
|
if response:
|
||||||
|
response = response["projects"]
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def getRole(self, id):
|
||||||
|
try:
|
||||||
|
response = self.getResource("/roles/%s" % id, "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the role info (id=%r): %s"
|
||||||
|
% (id, response["error"]["message"]))
|
||||||
|
|
||||||
|
if response:
|
||||||
|
response = response["role"]
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def getRoles(self):
|
||||||
|
try:
|
||||||
|
response = self.getResource("/roles", "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the roles list: %s"
|
||||||
|
% response["error"]["message"])
|
||||||
|
|
||||||
|
if response:
|
||||||
|
response = response["roles"]
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def getToken(self):
|
||||||
|
self.authenticate()
|
||||||
|
return self.token
|
||||||
|
|
||||||
|
def deleteToken(self, id):
|
||||||
|
if self.token is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
headers = {"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": "python-novaclient",
|
||||||
|
"X-Auth-Project-Id": self.token.getProject()["name"],
|
||||||
|
"X-Auth-Token": self.token.getId(),
|
||||||
|
"X-Subject-Token": id}
|
||||||
|
|
||||||
|
response = requests.delete(url=self.auth_url + "/auth/tokens",
|
||||||
|
headers=headers,
|
||||||
|
timeout=self.timeout)
|
||||||
|
|
||||||
|
self.token = None
|
||||||
|
|
||||||
|
if response.status_code != requests.codes.ok:
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
def validateToken(self, id):
|
||||||
|
self.authenticate()
|
||||||
|
|
||||||
|
headers = {"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": "python-novaclient",
|
||||||
|
"X-Auth-Project-Id": self.token.getProject()["name"],
|
||||||
|
"X-Auth-Token": self.token.getId(),
|
||||||
|
"X-Subject-Token": id}
|
||||||
|
|
||||||
|
response = requests.get(url=self.auth_url + "/auth/tokens",
|
||||||
|
headers=headers,
|
||||||
|
timeout=self.timeout)
|
||||||
|
|
||||||
|
if response.status_code != requests.codes.ok:
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
if not response.text:
|
||||||
|
raise Exception("token not found!")
|
||||||
|
|
||||||
|
token_subject = response.headers["X-Subject-Token"]
|
||||||
|
token_data = response.json()
|
||||||
|
|
||||||
|
return Token(token_subject, token_data)
|
||||||
|
|
||||||
|
def getEndpoint(self, id=None, service_id=None):
|
||||||
|
if id:
|
||||||
|
try:
|
||||||
|
response = self.getResource("/endpoints/%s" % id, "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the endpoint (id=%r): %s"
|
||||||
|
% (id, response["error"]["message"]))
|
||||||
|
if response:
|
||||||
|
response = response["endpoint"]
|
||||||
|
|
||||||
|
return response
|
||||||
|
elif service_id:
|
||||||
|
try:
|
||||||
|
endpoints = self.getEndpoints()
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the endpoints list"
|
||||||
|
"(serviceId=%r): %s"
|
||||||
|
% response["error"]["message"])
|
||||||
|
|
||||||
|
if endpoints:
|
||||||
|
for endpoint in endpoints:
|
||||||
|
if endpoint["service_id"] == service_id:
|
||||||
|
return endpoint
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getEndpoints(self):
|
||||||
|
try:
|
||||||
|
response = self.getResource("/endpoints", "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the endpoints list: %s"
|
||||||
|
% response["error"]["message"])
|
||||||
|
|
||||||
|
if response:
|
||||||
|
response = response["endpoints"]
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def getService(self, id=None, name=None):
|
||||||
|
if id:
|
||||||
|
try:
|
||||||
|
response = self.getResource("/services/%s" % id, "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the service info (id=%r)"
|
||||||
|
": %s" % (id, response["error"]["message"]))
|
||||||
|
|
||||||
|
if response:
|
||||||
|
response = response["service"]
|
||||||
|
return response
|
||||||
|
elif name:
|
||||||
|
services = self.getServices()
|
||||||
|
|
||||||
|
if services:
|
||||||
|
for service in services:
|
||||||
|
if service["name"] == name:
|
||||||
|
return service
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getServices(self):
|
||||||
|
try:
|
||||||
|
response = self.getResource("/services", "GET")
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
response = ex.response.json()
|
||||||
|
raise Exception("error on retrieving the services list: %s"
|
||||||
|
% response["error"]["message"])
|
||||||
|
|
||||||
|
if response:
|
||||||
|
response = response["services"]
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def getResource(self, resource, method, data=None):
|
||||||
|
self.authenticate()
|
||||||
|
|
||||||
|
url = self.auth_url + "/" + resource
|
||||||
|
|
||||||
|
headers = {"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": "python-novaclient",
|
||||||
|
"X-Auth-Project-Id": self.token.getProject()["name"],
|
||||||
|
"X-Auth-Token": self.token.getId()}
|
||||||
|
|
||||||
|
if method == "GET":
|
||||||
|
response = requests.get(url,
|
||||||
|
headers=headers,
|
||||||
|
params=data,
|
||||||
|
timeout=self.timeout)
|
||||||
|
elif method == "POST":
|
||||||
|
response = requests.post(url,
|
||||||
|
headers=headers,
|
||||||
|
data=json.dumps(data),
|
||||||
|
timeout=self.timeout)
|
||||||
|
elif method == "PUT":
|
||||||
|
response = requests.put(url,
|
||||||
|
headers=headers,
|
||||||
|
data=json.dumps(data),
|
||||||
|
timeout=self.timeout)
|
||||||
|
elif method == "HEAD":
|
||||||
|
response = requests.head(url,
|
||||||
|
headers=headers,
|
||||||
|
data=json.dumps(data),
|
||||||
|
timeout=self.timeout)
|
||||||
|
elif method == "DELETE":
|
||||||
|
response = requests.delete(url,
|
||||||
|
headers=headers,
|
||||||
|
data=json.dumps(data),
|
||||||
|
timeout=self.timeout)
|
||||||
|
else:
|
||||||
|
raise Exception("wrong HTTP method: %s" % method)
|
||||||
|
|
||||||
|
if response.status_code != requests.codes.ok:
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
if response.text:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
return None
|
204
synergy/client/shell.py
Normal file
204
synergy/client/shell.py
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from pkg_resources import iter_entry_points
|
||||||
|
from synergy.client import keystone_v3
|
||||||
|
|
||||||
|
__author__ = "Lisa Zangrando"
|
||||||
|
__email__ = "lisa.zangrando[AT]pd.infn.it"
|
||||||
|
__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud
|
||||||
|
All Rights Reserved
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0;
|
||||||
|
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."""
|
||||||
|
|
||||||
|
COMMANDS_ENTRY_POINT = "synergy.commands" # used to discover Synergy commands
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
parser = ArgumentParser(prog="synergy",
|
||||||
|
epilog="Command-line interface to the"
|
||||||
|
" OpenStack Synergy API.")
|
||||||
|
|
||||||
|
# Global arguments
|
||||||
|
parser.add_argument("--version", action="version", version="v1.0")
|
||||||
|
|
||||||
|
parser.add_argument("--debug",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="print debugging output")
|
||||||
|
|
||||||
|
parser.add_argument("--os-username",
|
||||||
|
metavar="<auth-user-name>",
|
||||||
|
default=os.environ.get("OS_USERNAME"),
|
||||||
|
help="defaults to env[OS_USERNAME]")
|
||||||
|
|
||||||
|
parser.add_argument("--os-password",
|
||||||
|
metavar="<auth-password>",
|
||||||
|
default=os.environ.get("OS_PASSWORD"),
|
||||||
|
help="defaults to env[OS_PASSWORD]")
|
||||||
|
|
||||||
|
parser.add_argument("--os-project-name",
|
||||||
|
metavar="<auth-project-name>",
|
||||||
|
default=os.environ.get("OS_PROJECT_NAME"),
|
||||||
|
help="defaults to env[OS_PROJECT_NAME]")
|
||||||
|
|
||||||
|
parser.add_argument("--os-project-id",
|
||||||
|
metavar="<auth-project-id>",
|
||||||
|
default=os.environ.get("OS_PROJECT_ID"),
|
||||||
|
help="defaults to env[OS_PROJECT_ID]")
|
||||||
|
|
||||||
|
parser.add_argument("--os-auth-token",
|
||||||
|
metavar="<auth-token>",
|
||||||
|
default=os.environ.get("OS_AUTH_TOKEN", None),
|
||||||
|
help="defaults to env[OS_AUTH_TOKEN]")
|
||||||
|
|
||||||
|
parser.add_argument('--os-auth-token-cache',
|
||||||
|
default=os.environ.get("OS_AUTH_TOKEN_CACHE",
|
||||||
|
False),
|
||||||
|
action='store_true',
|
||||||
|
help="Use the auth token cache. Defaults to False "
|
||||||
|
"if env[OS_AUTH_TOKEN_CACHE] is not set")
|
||||||
|
|
||||||
|
parser.add_argument("--os-auth-url",
|
||||||
|
metavar="<auth-url>",
|
||||||
|
default=os.environ.get("OS_AUTH_URL"),
|
||||||
|
help="defaults to env[OS_AUTH_URL]")
|
||||||
|
|
||||||
|
parser.add_argument("--os-auth-system",
|
||||||
|
metavar="<auth-system>",
|
||||||
|
default=os.environ.get("OS_AUTH_SYSTEM"),
|
||||||
|
help="defaults to env[OS_AUTH_SYSTEM]")
|
||||||
|
|
||||||
|
parser.add_argument("--bypass-url",
|
||||||
|
metavar="<bypass-url>",
|
||||||
|
dest="bypass_url",
|
||||||
|
help="use this API endpoint instead of the "
|
||||||
|
"Service Catalog")
|
||||||
|
|
||||||
|
parser.add_argument("--os-cacert",
|
||||||
|
metavar="<ca-certificate>",
|
||||||
|
default=os.environ.get("OS_CACERT", None),
|
||||||
|
help="Specify a CA bundle file to use in verifying"
|
||||||
|
" a TLS (https) server certificate. Defaults "
|
||||||
|
"to env[OS_CACERT]")
|
||||||
|
"""
|
||||||
|
parser.add_argument("--insecure",
|
||||||
|
default=os.environ.get("INSECURE", False),
|
||||||
|
action="store_true",
|
||||||
|
help="explicitly allow Synergy's client to perform"
|
||||||
|
" \"insecure\" SSL (https) requests. The "
|
||||||
|
"server's certificate will not be verified "
|
||||||
|
"against any certificate authorities. This "
|
||||||
|
"option should be used with caution.")
|
||||||
|
"""
|
||||||
|
|
||||||
|
subparser = parser.add_subparsers(help="commands", dest="command_name")
|
||||||
|
commands = {}
|
||||||
|
|
||||||
|
for entry in iter_entry_points(COMMANDS_ENTRY_POINT):
|
||||||
|
command_class = entry.load()
|
||||||
|
command = command_class()
|
||||||
|
# command = command_class(*args, **kwargs)
|
||||||
|
command.configureParser(subparser)
|
||||||
|
commands[entry.name] = command
|
||||||
|
|
||||||
|
args = parser.parse_args(sys.argv[1:])
|
||||||
|
|
||||||
|
# print("args %s" % args)
|
||||||
|
|
||||||
|
os_username = args.os_username
|
||||||
|
os_password = args.os_password
|
||||||
|
os_project_name = args.os_project_name
|
||||||
|
# os_project_id = args.os_project_id
|
||||||
|
os_auth_token = args.os_auth_token
|
||||||
|
os_auth_token_cache = args.os_auth_token_cache
|
||||||
|
os_auth_url = args.os_auth_url
|
||||||
|
# os_auth_system = args.os_auth_system
|
||||||
|
# insecure = args.insecure
|
||||||
|
bypass_url = args.bypass_url
|
||||||
|
# cacert = args.os_cacert
|
||||||
|
command_name = args.command_name
|
||||||
|
|
||||||
|
if not os_username:
|
||||||
|
raise Exception("'os-username' not defined!")
|
||||||
|
|
||||||
|
if not os_password:
|
||||||
|
raise Exception("'os-password' not defined!")
|
||||||
|
|
||||||
|
if not os_project_name:
|
||||||
|
raise Exception("'os-project-name' not defined!")
|
||||||
|
|
||||||
|
if not os_auth_url:
|
||||||
|
raise Exception("'os-auth-url' not defined!")
|
||||||
|
|
||||||
|
client = keystone_v3.KeystoneClient(auth_url=os_auth_url,
|
||||||
|
username=os_username,
|
||||||
|
password=os_password,
|
||||||
|
project_name=os_project_name)
|
||||||
|
"""
|
||||||
|
client.authenticate()
|
||||||
|
|
||||||
|
token = client.getToken()
|
||||||
|
|
||||||
|
print("os_auth_token=%s" % os_auth_token)
|
||||||
|
print("os_auth_token_cache=%s" % os_auth_token_cache)
|
||||||
|
"""
|
||||||
|
token = None
|
||||||
|
|
||||||
|
if os_auth_token:
|
||||||
|
token = os_auth_token
|
||||||
|
elif os_auth_token_cache:
|
||||||
|
token = keystone_v3.Token.load(".auth_token")
|
||||||
|
# print("token is expired? %s" % token.isExpired())
|
||||||
|
if token is None or token.isExpired():
|
||||||
|
client.authenticate()
|
||||||
|
token = client.getToken()
|
||||||
|
token.save(".auth_token")
|
||||||
|
else:
|
||||||
|
client.authenticate()
|
||||||
|
token = client.getToken()
|
||||||
|
|
||||||
|
synergy_url = None
|
||||||
|
if bypass_url:
|
||||||
|
synergy_url = bypass_url
|
||||||
|
else:
|
||||||
|
synergy_service = client.getService(name="synergy")
|
||||||
|
|
||||||
|
synergy_endpoint = client.getEndpoint(
|
||||||
|
service_id=synergy_service["id"])
|
||||||
|
|
||||||
|
synergy_url = synergy_endpoint["url"]
|
||||||
|
|
||||||
|
if command_name not in commands:
|
||||||
|
print("command %r not found!" % command_name)
|
||||||
|
|
||||||
|
commands[command_name].sendRequest(synergy_url, args)
|
||||||
|
commands[command_name].log()
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
print("Shutting down synergyclient")
|
||||||
|
sys.exit(1)
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
print("HTTPError: %s" % e.response._content)
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print("ERROR: %s" % e)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -240,6 +240,56 @@ class Synergy(service.Service):
|
|||||||
manager_name = None
|
manager_name = None
|
||||||
command = None
|
command = None
|
||||||
|
|
||||||
|
query = environ.get("QUERY_STRING", None)
|
||||||
|
|
||||||
|
if query:
|
||||||
|
parameters = parse_qs(query)
|
||||||
|
LOG.info(parameters)
|
||||||
|
if "manager" in parameters:
|
||||||
|
manager_name = escape(parameters['manager'][0])
|
||||||
|
|
||||||
|
if "command" in parameters:
|
||||||
|
command = escape(parameters['command'][0])
|
||||||
|
|
||||||
|
if "args" in parameters:
|
||||||
|
manager_args = escape(parameters['args'][0])
|
||||||
|
manager_args = manager_args.replace("'", "\"")
|
||||||
|
manager_args = json.loads(manager_args)
|
||||||
|
else:
|
||||||
|
manager_args = {}
|
||||||
|
|
||||||
|
if not query or not manager_name or not command:
|
||||||
|
start_response("404 NOT FOUND", [("Content-Type", "text/plain")])
|
||||||
|
return ["wrong command"]
|
||||||
|
|
||||||
|
if manager_name in self.managers:
|
||||||
|
manager = self.managers[manager_name]
|
||||||
|
try:
|
||||||
|
result = manager.execute(command=command, **manager_args)
|
||||||
|
|
||||||
|
if not isinstance(result, dict):
|
||||||
|
try:
|
||||||
|
result = result.toDict()
|
||||||
|
except Exception:
|
||||||
|
result = result.__dict__
|
||||||
|
|
||||||
|
LOG.info("command result %s" % result)
|
||||||
|
|
||||||
|
start_response("200 OK", [("Content-Type", "text/html")])
|
||||||
|
return ["%s" % json.dumps(result)]
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.info("executeCommand error: %s" % ex)
|
||||||
|
start_response("404 NOT FOUND",
|
||||||
|
[("Content-Type", "text/plain")])
|
||||||
|
return ["error: %s" % ex]
|
||||||
|
else:
|
||||||
|
start_response("404 NOT FOUND", [("Content-Type", "text/plain")])
|
||||||
|
return ["manager %r not found!" % manager_name]
|
||||||
|
|
||||||
|
def executeCommand2(self, environ, start_response):
|
||||||
|
manager_name = None
|
||||||
|
command = None
|
||||||
|
|
||||||
synergySerializer = serializer.SynergySerializer()
|
synergySerializer = serializer.SynergySerializer()
|
||||||
query = environ.get("QUERY_STRING", None)
|
query = environ.get("QUERY_STRING", None)
|
||||||
# LOG.info("QUERY_STRING %s" % query)
|
# LOG.info("QUERY_STRING %s" % query)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user