Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
slapos.core
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Romain Courteaud
slapos.core
Commits
27f4aa34
Commit
27f4aa34
authored
Jul 16, 2019
by
Rafael Monnerat
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
slapos/slap: Split code into multiple meaningfull files before refactor
parent
ee43c187
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
396 additions
and
299 deletions
+396
-299
slapos/slap/exception.py
slapos/slap/exception.py
+53
-0
slapos/slap/hateoas.py
slapos/slap/hateoas.py
+313
-0
slapos/slap/slap.py
slapos/slap/slap.py
+6
-299
slapos/slap/util.py
slapos/slap/util.py
+24
-0
No files found.
slapos/slap/exception.py
0 → 100644
View file @
27f4aa34
# -*- coding: utf-8 -*-
# vim: set et sts=2:
##############################################################################
#
# Copyright (c) 2010, 2011, 2012 Vifib SARL and Contributors.
# All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2.1
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
from
zope.interface
import
implementer
from
.interface
import
slap
as
interface
"""Exposed exceptions"""
@
implementer
(
interface
.
IResourceNotReady
)
class
ResourceNotReady
(
Exception
):
pass
@
implementer
(
interface
.
IServerError
)
class
ServerError
(
Exception
):
pass
@
implementer
(
interface
.
INotFoundError
)
class
NotFoundError
(
Exception
):
pass
class
AuthenticationError
(
Exception
):
pass
@
implementer
(
interface
.
IConnectionError
)
class
ConnectionError
(
Exception
):
pass
slapos/slap/hateoas.py
0 → 100644
View file @
27f4aa34
# -*- coding: utf-8 -*-
# vim: set et sts=2:
##############################################################################
#
# Copyright (c) 2019 Vifib SARL and Contributors.
# All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2.1
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import
json
import
six
from
six.moves.urllib
import
parse
from
uritemplate
import
expand
import
os
import
logging
from
.util
import
_addIpv6Brackets
from
.exception
import
ResourceNotReady
,
NotFoundError
,
\
AuthenticationError
,
ConnectionError
import
requests
# silence messages like 'Unverified HTTPS request is being made'
requests
.
packages
.
urllib3
.
disable_warnings
()
# silence messages like 'Starting connection' that are logged with INFO
urllib3_logger
=
logging
.
getLogger
(
'requests.packages.urllib3'
)
urllib3_logger
.
setLevel
(
logging
.
WARNING
)
from
cachecontrol
import
CacheControl
from
cachecontrol.caches.file_cache
import
FileCache
# XXX fallback_logger to be deprecated together with the old CLI entry points.
fallback_logger
=
logging
.
getLogger
(
__name__
)
fallback_handler
=
logging
.
StreamHandler
()
fallback_logger
.
setLevel
(
logging
.
INFO
)
fallback_logger
.
addHandler
(
fallback_handler
)
class
ConnectionHelper
:
def
__init__
(
self
,
master_url
,
key_file
=
None
,
cert_file
=
None
,
master_ca_file
=
None
,
timeout
=
None
):
master_url
=
_addIpv6Brackets
(
master_url
)
if
master_url
.
endswith
(
'/'
):
self
.
slapgrid_uri
=
master_url
else
:
# add a slash or the last path segment will be ignored by urljoin
self
.
slapgrid_uri
=
master_url
+
'/'
self
.
key_file
=
key_file
self
.
cert_file
=
cert_file
self
.
master_ca_file
=
master_ca_file
self
.
timeout
=
timeout
# self.session will handle requests using HTTP Cache Control rules.
self
.
uncached_session
=
requests
.
Session
()
self
.
session
=
CacheControl
(
self
.
uncached_session
,
cache
=
FileCache
(
os
.
path
.
expanduser
(
"~/.slapos_cached_get"
)))
def
do_request
(
self
,
method
,
path
,
params
=
None
,
data
=
None
,
headers
=
None
):
url
=
parse
.
urljoin
(
self
.
slapgrid_uri
,
path
)
if
headers
is
None
:
headers
=
{}
headers
.
setdefault
(
'Accept'
,
'*/*'
)
if
path
.
startswith
(
'/'
):
path
=
path
[
1
:]
# raise ValueError('method path should be relative: %s' % path)
try
:
if
url
.
startswith
(
'https'
):
cert
=
(
self
.
cert_file
,
self
.
key_file
)
else
:
cert
=
None
# XXX TODO: handle host cert verify
# Old behavior was to pass empty parameters as "None" value.
# Behavior kept for compatibility with old slapproxies (< v1.3.3).
# Can be removed when old slapproxies are no longer in use.
if
data
:
for
k
,
v
in
six
.
iteritems
(
data
):
if
v
is
None
:
data
[
k
]
=
'None'
req
=
method
(
url
=
url
,
params
=
params
,
cert
=
cert
,
verify
=
False
,
data
=
data
,
headers
=
headers
,
timeout
=
self
.
timeout
)
try
:
req
.
raise_for_status
()
except
TypeError
:
# In Py3, a comparison between NoneType and int can occur if req has no
# status_code (= None).
pass
except
(
requests
.
Timeout
,
requests
.
ConnectionError
)
as
exc
:
raise
ConnectionError
(
"Couldn't connect to the server. Please "
"double check given master-url argument, and make sure that IPv6 is "
"enabled on your machine and that the server is available. The "
"original error was:
\
n
%s"
%
exc
)
except
requests
.
HTTPError
as
exc
:
if
exc
.
response
.
status_code
==
requests
.
status_codes
.
codes
.
not_found
:
msg
=
url
if
params
:
msg
+=
' - %s'
%
params
raise
NotFoundError
(
msg
)
elif
exc
.
response
.
status_code
==
requests
.
status_codes
.
codes
.
request_timeout
:
# this is explicitly returned by SlapOS master, and does not really mean timeout
raise
ResourceNotReady
(
path
)
# XXX TODO test request timeout and resource not found
else
:
# we don't know how or don't want to handle these (including Unauthorized)
req
.
raise_for_status
()
except
requests
.
exceptions
.
SSLError
as
exc
:
raise
AuthenticationError
(
"%s
\
n
Couldn't authenticate computer. Please "
"check that certificate and key exist and are valid."
%
exc
)
# XXX TODO parse server messages for client configure and node register
# elif response.status != httplib.OK:
# message = parsed_error_message(response.status,
# response.read(),
# path)
# raise ServerError(message)
return
req
def
GET
(
self
,
path
,
params
=
None
,
headers
=
None
):
req
=
self
.
do_request
(
self
.
session
.
get
,
path
=
path
,
params
=
params
,
headers
=
headers
)
return
req
.
text
.
encode
(
'utf-8'
)
def
POST
(
self
,
path
,
params
=
None
,
data
=
None
,
content_type
=
'application/x-www-form-urlencoded'
):
req
=
self
.
do_request
(
requests
.
post
,
path
=
path
,
params
=
params
,
data
=
data
,
headers
=
{
'Content-type'
:
content_type
})
return
req
.
text
.
encode
(
'utf-8'
)
class
HateoasNavigator
(
object
):
"""
Navigator for HATEOAS-style APIs.
Inspired by
https://git.erp5.org/gitweb/jio.git/blob/HEAD:/src/jio.storage/erp5storage.js
"""
# XXX: needs to be designed for real. For now, just a non-maintainable prototype.
# XXX: export to a standalone library, independant from slap.
def
__init__
(
self
,
slapgrid_uri
,
key_file
=
None
,
cert_file
=
None
,
master_ca_file
=
None
,
timeout
=
60
):
self
.
slapos_master_hateoas_uri
=
slapgrid_uri
self
.
key_file
=
key_file
self
.
cert_file
=
cert_file
self
.
master_ca_file
=
master_ca_file
self
.
timeout
=
timeout
def
GET
(
self
,
uri
,
headers
=
None
):
connection_helper
=
ConnectionHelper
(
uri
,
self
.
key_file
,
self
.
cert_file
,
self
.
master_ca_file
,
self
.
timeout
)
return
connection_helper
.
GET
(
uri
,
headers
=
headers
)
def
hateoasGetLinkFromLinks
(
self
,
links
,
title
):
if
type
(
links
)
==
dict
:
if
links
.
get
(
'title'
)
==
title
:
return
links
[
'href'
]
raise
NotFoundError
(
'Action %s not found.'
%
title
)
for
action
in
links
:
if
action
.
get
(
'title'
)
==
title
:
return
action
[
'href'
]
else
:
raise
NotFoundError
(
'Action %s not found.'
%
title
)
def
getRelativeUrlFromUrn
(
self
,
urn
):
urn_schema
=
'urn:jio:get:'
try
:
_
,
url
=
urn
.
split
(
urn_schema
)
except
ValueError
:
return
return
str
(
url
)
def
getSiteDocument
(
self
,
url
,
headers
=
None
):
result
=
self
.
GET
(
url
,
headers
)
return
json
.
loads
(
result
)
def
getRootDocument
(
self
):
# XXX what about cache?
cached_root_document
=
getattr
(
self
,
'root_document'
,
None
)
if
cached_root_document
:
return
cached_root_document
self
.
root_document
=
self
.
getSiteDocument
(
self
.
slapos_master_hateoas_uri
,
headers
=
{
'Cache-Control'
:
'no-cache'
}
)
return
self
.
root_document
def
getDocumentAndHateoas
(
self
,
relative_url
,
view
=
'view'
):
site_document
=
self
.
getRootDocument
()
return
expand
(
site_document
[
'_links'
][
'traverse'
][
'href'
],
dict
(
relative_url
=
relative_url
,
view
=
view
)
)
def
getMeDocument
(
self
):
person_relative_url
=
self
.
getRelativeUrlFromUrn
(
self
.
getRootDocument
()[
'_links'
][
'me'
][
'href'
])
person_url
=
self
.
getDocumentAndHateoas
(
person_relative_url
)
return
json
.
loads
(
self
.
GET
(
person_url
))
class
SlapHateoasNavigator
(
HateoasNavigator
):
def
_hateoas_getHostingSubscriptionDict
(
self
):
action_object_slap_list
=
self
.
getMeDocument
()[
'_links'
][
'action_object_slap'
]
for
action
in
action_object_slap_list
:
if
action
.
get
(
'title'
)
==
'getHateoasHostingSubscriptionList'
:
getter_link
=
action
[
'href'
]
break
else
:
raise
Exception
(
'Hosting subscription not found.'
)
result
=
self
.
GET
(
getter_link
)
return
json
.
loads
(
result
)[
'_links'
][
'content'
]
# XXX rename me to blablaUrl(self)
def
_hateoas_getRelatedHostingSubscription
(
self
):
action_object_slap_list
=
self
.
getMeDocument
()[
'_links'
][
'action_object_slap'
]
getter_link
=
self
.
hateoasGetLinkFromLinks
(
action_object_slap_list
,
'getHateoasRelatedHostingSubscription'
)
result
=
self
.
GET
(
getter_link
)
return
json
.
loads
(
result
)[
'_links'
][
'action_object_jump'
][
'href'
]
def
_hateoasGetInformation
(
self
,
url
):
result
=
self
.
GET
(
url
)
result
=
json
.
loads
(
result
)
object_link
=
self
.
hateoasGetLinkFromLinks
(
result
[
'_links'
][
'action_object_slap'
],
'getHateoasInformation'
)
result
=
self
.
GET
(
object_link
)
return
json
.
loads
(
result
)
def
getHateoasInstanceList
(
self
,
hosting_subscription_url
):
hosting_subscription
=
json
.
loads
(
self
.
GET
(
hosting_subscription_url
))
instance_list_url
=
self
.
hateoasGetLinkFromLinks
(
hosting_subscription
[
'_links'
][
'action_object_slap'
],
'getHateoasInstanceList'
)
instance_list
=
json
.
loads
(
self
.
GET
(
instance_list_url
))
return
instance_list
[
'_links'
][
'content'
]
def
getHostingSubscriptionDict
(
self
):
hosting_subscription_link_list
=
self
.
_hateoas_getHostingSubscriptionDict
()
hosting_subscription_dict
=
{}
for
hosting_subscription_link
in
hosting_subscription_link_list
:
raw_information
=
self
.
getHostingSubscriptionRootSoftwareInstanceInformation
(
hosting_subscription_link
[
'title'
])
software_instance
=
SoftwareInstance
()
# XXX redefine SoftwareInstance to be more consistent
for
key
,
value
in
raw_information
.
iteritems
():
if
key
in
[
'_links'
]:
continue
setattr
(
software_instance
,
'_%s'
%
key
,
value
)
setattr
(
software_instance
,
'_software_release_url'
,
raw_information
[
'_links'
][
'software_release'
])
hosting_subscription_dict
[
software_instance
.
_title
]
=
software_instance
return
hosting_subscription_dict
def
getHostingSubscriptionRootSoftwareInstanceInformation
(
self
,
reference
):
hosting_subscription_list
=
self
.
_hateoas_getHostingSubscriptionDict
()
for
hosting_subscription
in
hosting_subscription_list
:
if
hosting_subscription
.
get
(
'title'
)
==
reference
:
hosting_subscription_url
=
hosting_subscription
[
'href'
]
break
else
:
raise
NotFoundError
(
'This document does not exist.'
)
hosting_subscription
=
json
.
loads
(
self
.
GET
(
hosting_subscription_url
))
software_instance_url
=
self
.
hateoasGetLinkFromLinks
(
hosting_subscription
[
'_links'
][
'action_object_slap'
],
'getHateoasRootInstance'
)
response
=
self
.
GET
(
software_instance_url
)
response
=
json
.
loads
(
response
)
software_instance_url
=
response
[
'_links'
][
'content'
][
0
][
'href'
]
return
self
.
_hateoasGetInformation
(
software_instance_url
)
def
getRelatedInstanceInformation
(
self
,
reference
):
related_hosting_subscription_url
=
self
.
_hateoas_getRelatedHostingSubscription
()
instance_list
=
self
.
getHateoasInstanceList
(
related_hosting_subscription_url
)
instance_url
=
self
.
hateoasGetLinkFromLinks
(
instance_list
,
reference
)
instance
=
self
.
_hateoasGetInformation
(
instance_url
)
return
instance
slapos/slap/slap.py
View file @
27f4aa34
...
...
@@ -37,24 +37,23 @@ __all__ = ["slap", "ComputerPartition", "Computer", "SoftwareRelease",
"ResourceNotReady"
,
"ServerError"
,
"ConnectionError"
]
import
os
import
json
import
logging
import
re
import
hashlib
from
functools
import
wraps
import
six
from
six.moves.urllib
import
parse
from
.util
import
xml2dict
from
.exception
import
ResourceNotReady
,
ServerError
,
NotFoundError
,
\
ConnectionError
from
.hateoas
import
SlapHateoasNavigator
,
ConnectionHelper
from
slapos.util
import
loads
,
dumps
,
bytes2str
import
netaddr
from
xml.sax
import
saxutils
from
zope.interface
import
implementer
from
.interface
import
slap
as
interface
from
uritemplate
import
expand
import
requests
# silence messages like 'Unverified HTTPS request is being made'
...
...
@@ -63,8 +62,6 @@ requests.packages.urllib3.disable_warnings()
urllib3_logger
=
logging
.
getLogger
(
'requests.packages.urllib3'
)
urllib3_logger
.
setLevel
(
logging
.
WARNING
)
from
cachecontrol
import
CacheControl
from
cachecontrol.caches.file_cache
import
FileCache
# XXX fallback_logger to be deprecated together with the old CLI entry points.
fallback_logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -217,25 +214,6 @@ class SoftwareInstance(SlapDocument):
self
.
__dict__
.
update
(
kw
)
"""Exposed exceptions"""
@
implementer
(
interface
.
IResourceNotReady
)
class
ResourceNotReady
(
Exception
):
pass
@
implementer
(
interface
.
IServerError
)
class
ServerError
(
Exception
):
pass
@
implementer
(
interface
.
INotFoundError
)
class
NotFoundError
(
Exception
):
pass
class
AuthenticationError
(
Exception
):
pass
@
implementer
(
interface
.
IConnectionError
)
class
ConnectionError
(
Exception
):
pass
@
implementer
(
interface
.
ISupply
)
class
Supply
(
SlapDocument
):
...
...
@@ -670,45 +648,7 @@ class ComputerPartition(SlapRequester):
}
)
def
_addIpv6Brackets
(
url
):
# if master_url contains an ipv6 without bracket, add it
# Note that this is mostly to limit specific issues with
# backward compatiblity, not to ensure generic detection.
api_scheme
,
api_netloc
,
api_path
,
api_query
,
api_fragment
=
parse
.
urlsplit
(
url
)
try
:
ip
=
netaddr
.
IPAddress
(
api_netloc
)
port
=
None
except
netaddr
.
AddrFormatError
:
try
:
ip
=
netaddr
.
IPAddress
(
':'
.
join
(
api_netloc
.
split
(
':'
)[:
-
1
]))
port
=
api_netloc
.
split
(
':'
)[
-
1
]
except
netaddr
.
AddrFormatError
:
ip
=
port
=
None
if
ip
and
ip
.
version
==
6
:
api_netloc
=
'[%s]'
%
ip
if
port
:
api_netloc
=
'%s:%s'
%
(
api_netloc
,
port
)
url
=
parse
.
urlunsplit
((
api_scheme
,
api_netloc
,
api_path
,
api_query
,
api_fragment
))
return
url
class
ConnectionHelper
:
def
__init__
(
self
,
master_url
,
key_file
=
None
,
cert_file
=
None
,
master_ca_file
=
None
,
timeout
=
None
):
master_url
=
_addIpv6Brackets
(
master_url
)
if
master_url
.
endswith
(
'/'
):
self
.
slapgrid_uri
=
master_url
else
:
# add a slash or the last path segment will be ignored by urljoin
self
.
slapgrid_uri
=
master_url
+
'/'
self
.
key_file
=
key_file
self
.
cert_file
=
cert_file
self
.
master_ca_file
=
master_ca_file
self
.
timeout
=
timeout
# self.session will handle requests using HTTP Cache Control rules.
self
.
uncached_session
=
requests
.
Session
()
self
.
session
=
CacheControl
(
self
.
uncached_session
,
cache
=
FileCache
(
os
.
path
.
expanduser
(
"~/.slapos_cached_get"
)))
class
SlapConnectionHelper
(
ConnectionHelper
):
def
getComputerInformation
(
self
,
computer_id
):
xml
=
self
.
GET
(
'getComputerInformation'
,
params
=
{
'computer_id'
:
computer_id
})
...
...
@@ -733,93 +673,6 @@ class ConnectionHelper:
return
loads
(
xml
)
def
do_request
(
self
,
method
,
path
,
params
=
None
,
data
=
None
,
headers
=
None
):
url
=
parse
.
urljoin
(
self
.
slapgrid_uri
,
path
)
if
headers
is
None
:
headers
=
{}
headers
.
setdefault
(
'Accept'
,
'*/*'
)
if
path
.
startswith
(
'/'
):
path
=
path
[
1
:]
# raise ValueError('method path should be relative: %s' % path)
try
:
if
url
.
startswith
(
'https'
):
cert
=
(
self
.
cert_file
,
self
.
key_file
)
else
:
cert
=
None
# XXX TODO: handle host cert verify
# Old behavior was to pass empty parameters as "None" value.
# Behavior kept for compatibility with old slapproxies (< v1.3.3).
# Can be removed when old slapproxies are no longer in use.
if
data
:
for
k
,
v
in
six
.
iteritems
(
data
):
if
v
is
None
:
data
[
k
]
=
'None'
req
=
method
(
url
=
url
,
params
=
params
,
cert
=
cert
,
verify
=
False
,
data
=
data
,
headers
=
headers
,
timeout
=
self
.
timeout
)
try
:
req
.
raise_for_status
()
except
TypeError
:
# In Py3, a comparison between NoneType and int can occur if req has no
# status_code (= None).
pass
except
(
requests
.
Timeout
,
requests
.
ConnectionError
)
as
exc
:
raise
ConnectionError
(
"Couldn't connect to the server. Please "
"double check given master-url argument, and make sure that IPv6 is "
"enabled on your machine and that the server is available. The "
"original error was:
\
n
%s"
%
exc
)
except
requests
.
HTTPError
as
exc
:
if
exc
.
response
.
status_code
==
requests
.
status_codes
.
codes
.
not_found
:
msg
=
url
if
params
:
msg
+=
' - %s'
%
params
raise
NotFoundError
(
msg
)
elif
exc
.
response
.
status_code
==
requests
.
status_codes
.
codes
.
request_timeout
:
# this is explicitly returned by SlapOS master, and does not really mean timeout
raise
ResourceNotReady
(
path
)
# XXX TODO test request timeout and resource not found
else
:
# we don't know how or don't want to handle these (including Unauthorized)
req
.
raise_for_status
()
except
requests
.
exceptions
.
SSLError
as
exc
:
raise
AuthenticationError
(
"%s
\
n
Couldn't authenticate computer. Please "
"check that certificate and key exist and are valid."
%
exc
)
# XXX TODO parse server messages for client configure and node register
# elif response.status != httplib.OK:
# message = parsed_error_message(response.status,
# response.read(),
# path)
# raise ServerError(message)
return
req
def
GET
(
self
,
path
,
params
=
None
,
headers
=
None
):
req
=
self
.
do_request
(
self
.
session
.
get
,
path
=
path
,
params
=
params
,
headers
=
headers
)
return
req
.
text
.
encode
(
'utf-8'
)
def
POST
(
self
,
path
,
params
=
None
,
data
=
None
,
content_type
=
'application/x-www-form-urlencoded'
):
req
=
self
.
do_request
(
requests
.
post
,
path
=
path
,
params
=
params
,
data
=
data
,
headers
=
{
'Content-type'
:
content_type
})
return
req
.
text
.
encode
(
'utf-8'
)
getHateoasUrl_cache
=
{}
@
implementer
(
interface
.
slap
)
class
slap
:
...
...
@@ -832,7 +685,8 @@ class slap:
if
master_ca_file
:
raise
NotImplementedError
(
'Master certificate not verified in this version: %s'
%
master_ca_file
)
self
.
_connection_helper
=
ConnectionHelper
(
slapgrid_uri
,
key_file
,
cert_file
,
master_ca_file
,
timeout
)
self
.
_connection_helper
=
SlapConnectionHelper
(
slapgrid_uri
,
key_file
,
cert_file
,
master_ca_file
,
timeout
)
if
not
slapgrid_rest_uri
:
getHateoasUrl_cache_key
=
(
slapgrid_uri
,
key_file
,
cert_file
,
master_ca_file
,
timeout
)
...
...
@@ -934,150 +788,3 @@ class slap:
if
not
getattr
(
self
,
'_hateoas_navigator'
,
None
):
raise
Exception
(
'SlapOS Master Hateoas API required for this operation is not availble.'
)
return
self
.
_hateoas_navigator
.
getHostingSubscriptionDict
()
class
HateoasNavigator
(
object
):
"""
Navigator for HATEOAS-style APIs.
Inspired by
https://git.erp5.org/gitweb/jio.git/blob/HEAD:/src/jio.storage/erp5storage.js
"""
# XXX: needs to be designed for real. For now, just a non-maintainable prototype.
# XXX: export to a standalone library, independant from slap.
def
__init__
(
self
,
slapgrid_uri
,
key_file
=
None
,
cert_file
=
None
,
master_ca_file
=
None
,
timeout
=
60
):
self
.
slapos_master_hateoas_uri
=
slapgrid_uri
self
.
key_file
=
key_file
self
.
cert_file
=
cert_file
self
.
master_ca_file
=
master_ca_file
self
.
timeout
=
timeout
def
GET
(
self
,
uri
,
headers
=
None
):
connection_helper
=
ConnectionHelper
(
uri
,
self
.
key_file
,
self
.
cert_file
,
self
.
master_ca_file
,
self
.
timeout
)
return
connection_helper
.
GET
(
uri
,
headers
=
headers
)
def
hateoasGetLinkFromLinks
(
self
,
links
,
title
):
if
type
(
links
)
==
dict
:
if
links
.
get
(
'title'
)
==
title
:
return
links
[
'href'
]
raise
NotFoundError
(
'Action %s not found.'
%
title
)
for
action
in
links
:
if
action
.
get
(
'title'
)
==
title
:
return
action
[
'href'
]
else
:
raise
NotFoundError
(
'Action %s not found.'
%
title
)
def
getRelativeUrlFromUrn
(
self
,
urn
):
urn_schema
=
'urn:jio:get:'
try
:
_
,
url
=
urn
.
split
(
urn_schema
)
except
ValueError
:
return
return
str
(
url
)
def
getSiteDocument
(
self
,
url
,
headers
=
None
):
result
=
self
.
GET
(
url
,
headers
)
return
json
.
loads
(
result
)
def
getRootDocument
(
self
):
# XXX what about cache?
cached_root_document
=
getattr
(
self
,
'root_document'
,
None
)
if
cached_root_document
:
return
cached_root_document
self
.
root_document
=
self
.
getSiteDocument
(
self
.
slapos_master_hateoas_uri
,
headers
=
{
'Cache-Control'
:
'no-cache'
}
)
return
self
.
root_document
def
getDocumentAndHateoas
(
self
,
relative_url
,
view
=
'view'
):
site_document
=
self
.
getRootDocument
()
return
expand
(
site_document
[
'_links'
][
'traverse'
][
'href'
],
dict
(
relative_url
=
relative_url
,
view
=
view
)
)
def
getMeDocument
(
self
):
person_relative_url
=
self
.
getRelativeUrlFromUrn
(
self
.
getRootDocument
()[
'_links'
][
'me'
][
'href'
])
person_url
=
self
.
getDocumentAndHateoas
(
person_relative_url
)
return
json
.
loads
(
self
.
GET
(
person_url
))
class
SlapHateoasNavigator
(
HateoasNavigator
):
def
_hateoas_getHostingSubscriptionDict
(
self
):
action_object_slap_list
=
self
.
getMeDocument
()[
'_links'
][
'action_object_slap'
]
for
action
in
action_object_slap_list
:
if
action
.
get
(
'title'
)
==
'getHateoasHostingSubscriptionList'
:
getter_link
=
action
[
'href'
]
break
else
:
raise
Exception
(
'Hosting subscription not found.'
)
result
=
self
.
GET
(
getter_link
)
return
json
.
loads
(
result
)[
'_links'
][
'content'
]
# XXX rename me to blablaUrl(self)
def
_hateoas_getRelatedHostingSubscription
(
self
):
action_object_slap_list
=
self
.
getMeDocument
()[
'_links'
][
'action_object_slap'
]
getter_link
=
self
.
hateoasGetLinkFromLinks
(
action_object_slap_list
,
'getHateoasRelatedHostingSubscription'
)
result
=
self
.
GET
(
getter_link
)
return
json
.
loads
(
result
)[
'_links'
][
'action_object_jump'
][
'href'
]
def
_hateoasGetInformation
(
self
,
url
):
result
=
self
.
GET
(
url
)
result
=
json
.
loads
(
result
)
object_link
=
self
.
hateoasGetLinkFromLinks
(
result
[
'_links'
][
'action_object_slap'
],
'getHateoasInformation'
)
result
=
self
.
GET
(
object_link
)
return
json
.
loads
(
result
)
def
getHateoasInstanceList
(
self
,
hosting_subscription_url
):
hosting_subscription
=
json
.
loads
(
self
.
GET
(
hosting_subscription_url
))
instance_list_url
=
self
.
hateoasGetLinkFromLinks
(
hosting_subscription
[
'_links'
][
'action_object_slap'
],
'getHateoasInstanceList'
)
instance_list
=
json
.
loads
(
self
.
GET
(
instance_list_url
))
return
instance_list
[
'_links'
][
'content'
]
def
getHostingSubscriptionDict
(
self
):
hosting_subscription_link_list
=
self
.
_hateoas_getHostingSubscriptionDict
()
hosting_subscription_dict
=
{}
for
hosting_subscription_link
in
hosting_subscription_link_list
:
raw_information
=
self
.
getHostingSubscriptionRootSoftwareInstanceInformation
(
hosting_subscription_link
[
'title'
])
software_instance
=
SoftwareInstance
()
# XXX redefine SoftwareInstance to be more consistent
for
key
,
value
in
six
.
iteritems
(
raw_information
):
if
key
in
[
'_links'
]:
continue
setattr
(
software_instance
,
'_%s'
%
key
,
value
)
setattr
(
software_instance
,
'_software_release_url'
,
raw_information
[
'_links'
][
'software_release'
])
hosting_subscription_dict
[
software_instance
.
_title
]
=
software_instance
return
hosting_subscription_dict
def
getHostingSubscriptionRootSoftwareInstanceInformation
(
self
,
reference
):
hosting_subscription_list
=
self
.
_hateoas_getHostingSubscriptionDict
()
for
hosting_subscription
in
hosting_subscription_list
:
if
hosting_subscription
.
get
(
'title'
)
==
reference
:
hosting_subscription_url
=
hosting_subscription
[
'href'
]
break
else
:
raise
NotFoundError
(
'This document does not exist.'
)
hosting_subscription
=
json
.
loads
(
self
.
GET
(
hosting_subscription_url
))
software_instance_url
=
self
.
hateoasGetLinkFromLinks
(
hosting_subscription
[
'_links'
][
'action_object_slap'
],
'getHateoasRootInstance'
)
response
=
self
.
GET
(
software_instance_url
)
response
=
json
.
loads
(
response
)
software_instance_url
=
response
[
'_links'
][
'content'
][
0
][
'href'
]
return
self
.
_hateoasGetInformation
(
software_instance_url
)
def
getRelatedInstanceInformation
(
self
,
reference
):
related_hosting_subscription_url
=
self
.
_hateoas_getRelatedHostingSubscription
()
instance_list
=
self
.
getHateoasInstanceList
(
related_hosting_subscription_url
)
instance_url
=
self
.
hateoasGetLinkFromLinks
(
instance_list
,
reference
)
instance
=
self
.
_hateoasGetInformation
(
instance_url
)
return
instance
slapos/slap/util.py
View file @
27f4aa34
from
lxml
import
etree
from
six.moves.urllib
import
parse
import
netaddr
def
xml2dict
(
xml
):
result_dict
=
{}
...
...
@@ -14,3 +16,25 @@ def xml2dict(xml):
value
=
element
.
text
result_dict
[
key
]
=
value
return
result_dict
def
_addIpv6Brackets
(
url
):
# if master_url contains an ipv6 without bracket, add it
# Note that this is mostly to limit specific issues with
# backward compatiblity, not to ensure generic detection.
api_scheme
,
api_netloc
,
api_path
,
api_query
,
api_fragment
=
parse
.
urlsplit
(
url
)
try
:
ip
=
netaddr
.
IPAddress
(
api_netloc
)
port
=
None
except
netaddr
.
AddrFormatError
:
try
:
ip
=
netaddr
.
IPAddress
(
':'
.
join
(
api_netloc
.
split
(
':'
)[:
-
1
]))
port
=
api_netloc
.
split
(
':'
)[
-
1
]
except
netaddr
.
AddrFormatError
:
ip
=
port
=
None
if
ip
and
ip
.
version
==
6
:
api_netloc
=
'[%s]'
%
ip
if
port
:
api_netloc
=
'%s:%s'
%
(
api_netloc
,
port
)
url
=
parse
.
urlunsplit
((
api_scheme
,
api_netloc
,
api_path
,
api_query
,
api_fragment
))
return
url
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment