Commit aa4f440f authored by User's avatar User

Switch between authentication URLs (hardcoded)

parent 1fa7e54b
......@@ -13,7 +13,7 @@ snf-occi's documentation!
.. toctree::
:maxdepth: 2
About OCCI
About Open Cloud Computing Interface (OCCI)
----------
The current OCCI specification consists of the following three documents:
......
......@@ -6,9 +6,9 @@ setup(
description='OCCI to Openstack/Cyclades API bridge',
url='http://code.grnet.gr/projects/snf-occi',
license='BSD',
packages = ['snfOCCI'],
entry_points = {
'console_scripts' : ['snf-occi = snfOCCI.APIserver:main']
}
packages = ['snfOCCI','snfOCCI.snf_voms','snfOCCI.httpd','snfOCCI.snfServer','snfOCCI.extensions', 'astavomaki'],
entry_points = '''
[paste.app_factory]
snf_occi_app = snfOCCI:main
''',
)
This diff is collapsed.
"""
This it the entry point for paste deploy .
Paste config file needs to point to egg:<package name>:<entrypoint name>:
use = egg:snfOCCI#sample_app
sample_app entry point is defined in setup.py:
entry_points='''
[paste.app_factory]
sample_app = snf_voms:main
''',
which point to this function call (<module name>:function).
"""
# W0613:unused args
# pylint: disable=W0613
from snfOCCI import APIserver
#noinspection PyUnusedLocal
def main(global_config, **settings):
"""
This is the entry point for paste into the OCCI OS world.
"""
return APIserver.MyAPP()
\ No newline at end of file
# Copyright 2012-2013 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from snfOCCI.config import SERVER_CONFIG
from snfOCCI.extensions import snf_addons
from occi.backend import ActionBackend, KindBackend
from occi.extensions.infrastructure import START, STOP, SUSPEND, RESTART
from occi.exceptions import HTTPError
from kamaki.clients import ClientError
from occi.backend import ActionBackend, KindBackend, MixinBackend
from occi.core_model import Mixin
from occi.extensions import infrastructure
from occi.exceptions import HTTPError
from base64 import b64encode, b64decode
import json, yaml
#Compute Backend for snf-occi-server
......@@ -16,6 +55,11 @@ class MyBackend(KindBackend, ActionBackend):
def replace(self, old, new, extras):
raise HTTPError(501, "Replace is currently no applicable")
class SNFBackend(MixinBackend, ActionBackend):
pass
class ComputeBackend(MyBackend):
......@@ -29,28 +73,125 @@ class ComputeBackend(MyBackend):
try:
snf = extras['snf']
snf_network = extras['snf_network']
snf = extras['client']
for mixin in entity.mixins:
if mixin.related[0].term == 'os_tpl':
image_id = mixin.attributes['occi.core.id']
if mixin.related[0].term == 'resource_tpl':
flavor = mixin
flavor_id = mixin.attributes['occi.core.id']
print mixin
if 'occi.core.id' in mixin.attributes:
if mixin.related[0].term == 'os_tpl':
image_id = mixin.attributes['occi.core.id']
elif mixin.related[0].term == 'resource_tpl':
flavor = mixin
flavor_id = mixin.attributes['occi.core.id']
vm_name = entity.attributes['occi.core.title']
info = snf.create_server(vm_name, flavor_id, image_id)
entity.actions = [START]
user_data = None
user_pub_key = None
meta_json = None
personality = []
if entity.attributes.has_key('org.openstack.compute.user_data'):
user_data = b64decode(entity.attributes['org.openstack.compute.user_data'])
#user_data = entity.attributes['org.openstack.compute.user_data']
if entity.attributes.has_key('org.openstack.credentials.publickey.data'):
user_pub_key = entity.attributes['org.openstack.credentials.publickey.data']
# Implementation for the meta.json file to use the respective NoCloud cloudinit driver
# if user_data and user_pub_key:
# meta_json = json.dumps({'dsmode':'net','public-keys':user_pub_key,'user-data': user_data}, sort_keys=True,indent=4, separators=(',', ': ') )
# elif user_data:
# meta_json = json.dumps({'dsmode':'net','user-data': user_data}, sort_keys=True,indent=4, separators=(',', ': ') )
# elif user_pub_key:
# meta_json = json.dumps({'dsmode':'net','public-keys':user_pub_key}, sort_keys=True,indent=4, separators=(',', ': ') )
# if meta_json:
# print "!!!!!!!!!!!!!!!!!!!!!!!!!!!! CONTEXTUALIZATION!!!!!!!!!!!!!!!!!!!!!!"
# personality.append({'contents':b64encode(meta_json),
# 'path':' /var/lib/cloud/seed/config_drive/meta.js'})
# info = snf.create_server(vm_name, flavor_id, image_id,personality=personality)
# else:
# info = snf.create_server(vm_name, flavor_id, image_id)
if user_data:
#userDataDict = dict([('user-data', user_data)])
#userData = yaml.dump(userDataDict)
userData = user_data
if user_pub_key:
pub_keyDict = dict([('public-keys',user_pub_key)])
pub_key = yaml.dump(pub_keyDict)
# kwargs = dict(project_id='6d9ec935-fcd4-4ae1-a3a0-10e612c4f867')
kwargs = dict(project_id=extras.get('snf_project', None))
if user_data and user_pub_key:
print "!!!!!!!!!!!!!!!!!!!!!!!!!!!! CONTEXTUALIZATION - USER DATA & PUBLIC KEY!!!!!!!!!!!!!!!!!!!!!!"
personality.append({'contents':b64encode(userData),
'path':'/var/lib/cloud/seed/nocloud-net/user-data'})
personality.append({'contents':b64encode(pub_key),
'path':'/var/lib/cloud/seed/nocloud-net/meta-data'})
info = snf.create_server(vm_name, flavor_id, image_id,personality=personality, **kwargs)
elif user_data:
print "!!!!!!!!!!!!!!!!!!!!!!!!!!!! CONTEXTUALIZATION - USER DATA!!!!!!!!!!!!!!!!!!!!!!"
personality.append({'contents':b64encode(userData),
'path':'/var/lib/cloud/seed/nocloud-net/user-data'})
info = snf.create_server(vm_name, flavor_id, image_id,personality=personality, **kwargs)
elif user_pub_key:
print "!!!!!!!!!!!!!!!!!!!!!!!!!!!! CONTEXTUALIZATION - PUBLIC KEY!!!!!!!!!!!!!!!!!!!!!!"
personality.append({'contents':b64encode(pub_key),
'path':'/var/lib/cloud/seed/nocloud-net/meta-data'})
for retries in range(2):
try:
info = snf.create_server(vm_name, flavor_id, image_id,personality=personality, **kwargs)
except ClientError as ce:
if ce.status in (409, ):
print 'ce, create an IP and retry'.format(ce=ce)
snf_network.create_floatingip(**kwargs)
else:
raise ce
else:
print 'Create a server with some attributes...'
for retries in range(2):
try:
info = snf.create_server(vm_name, flavor_id, image_id, **kwargs)
break
except ClientError as ce:
if ce.status in (409, ):
print '{ce}, create an IP and retry'.format(ce=ce)
snf_network.create_floatingip(**kwargs)
else:
raise ce
entity.attributes['occi.compute.state'] = 'inactive'
entity.attributes['occi.core.id'] = str(info['id'])
entity.attributes['occi.compute.architecture'] = SERVER_CONFIG['compute_arch']
entity.attributes['occi.compute.cores'] = flavor.attributes['occi.compute.cores']
entity.attributes['occi.compute.memory'] = flavor.attributes['occi.compute.memory']
entity.attributes['occi.compute.hostname'] = SERVER_CONFIG['hostname'] % {'id':info['id']}
entity.actions = [infrastructure.STOP,
infrastructure.SUSPEND,
infrastructure.RESTART]
# entity.attributes['occi.compute.hostname'] = SERVER_CONFIG['hostname'] % {'id':info['id']}
info['adminPass']= ""
print info
networkIDs = info['addresses'].keys()
#resource.attributes['occi.compute.hostname'] = SERVER_CONFIG['hostname'] % {'id':int(key)}
if len(networkIDs)>0:
entity.attributes['occi.compute.hostname'] = str(info['addresses'][networkIDs[0]][0]['addr'])
else:
entity.attributes['occi.compute.hostname'] = ""
except (UnboundLocalError, KeyError) as e:
raise HTTPError(406, 'Missing details about compute instance')
def retrieve(self, entity, extras):
......@@ -65,9 +206,11 @@ class ComputeBackend(MyBackend):
status_dict = {'ACTIVE' : 'active',
'STOPPED' : 'inactive',
'REBOOT' : 'inactive',
'ERROR' : 'inactive',
'BUILD' : 'inactive',
'DELETED' : 'inactive',
'UNKNOWN' : 'inactive'
}
entity.attributes['occi.compute.state'] = status_dict[vm_state]
......@@ -77,21 +220,47 @@ class ComputeBackend(MyBackend):
else:
if entity.attributes['occi.compute.state'] == 'inactive':
entity.actions = [START]
entity.actions = [infrastructure.START]
if entity.attributes['occi.compute.state'] == 'active':
entity.actions = [STOP, SUSPEND, RESTART]
entity.actions = [infrastructure.STOP, infrastructure.SUSPEND, infrastructure.RESTART]
def delete(self, entity, extras):
#Deleting compute instance
snf = extras['snf']
vm_id = int(entity.attributes['occi.core.id'])
snf.delete_server(vm_id)
print "Deleting VM" + str(vm_id)
def action(self, entity, action, extras):
def get_vm_actions(self, entity ,vm_state):
actions = []
status_dict = {'ACTIVE' : 'active',
'STOPPED' : 'inactive',
'REBOOT' : 'inactive',
'ERROR' : 'inactive',
'BUILD' : 'inactive',
'DELETED' : 'inactive',
'UNKNOWN' : 'inactive'
}
if vm_state in status_dict:
entity.attributes['occi.compute.state'] = status_dict[vm_state]
if vm_state == 'ACTIVE':
actions.append(infrastructure.STOP)
actions.append(infrastructure.RESTART)
elif vm_state in ('STOPPED'):
actions.append(infrastructure.START)
return actions
else:
raise HTTPError(500, 'Undefined status of the VM')
def action(self, entity, action, attributes, extras):
#Triggering action to compute instances
......@@ -101,26 +270,31 @@ class ComputeBackend(MyBackend):
vm_id = int(entity.attributes['occi.core.id'])
vm_info = snf.get_server_details(vm_id)
vm_state = vm_info['status']
# Define the allowed actions depending on the state of the VM
entity.actions = self.get_vm_actions(entity,vm_state)
if vm_state == 'ERROR':
raise HTTPError(500, 'ERROR building the compute instance')
else:
if action not in entity.actions:
raise AttributeError("This action is currently no applicable.")
raise AttributeError("This action is currently no applicable in the current status of the VM (CURRENT_STATE = " + str(vm_state)+ ").")
elif action == START:
print "Starting VM"
elif action == infrastructure.START:
print "Starting VM" + str(vm_id)
client.start_server(vm_id)
elif action == STOP:
print "Stopping VM"
elif action == infrastructure.STOP:
print "Stopping VM" + str(vm_id)
client.shutdown_server(vm_id)
elif action == RESTART:
print "Restarting VM"
elif action == infrastructure.RESTART:
print "Restarting VM" + str(vm_id)
snf.reboot_server(vm_id)
elif action == SUSPEND:
elif action == infrastructure.SUSPEND:
raise HTTPError(501, "This actions is currently no applicable")
SERVER_CONFIG = {
'port': 8888,
'hostname': 'snf-%(id)d.vm.okeanos.grnet.gr',
'port': 9000,
'hostname': 'snf-136122.vm.okeanos.grnet.gr',
'compute_arch': 'x86'
}
KAMAKI_CONFIG = {
'compute_url': 'https://cyclades.okeanos.grnet.gr/api/v1.1'
'compute_url': 'https://cyclades.okeanos.grnet.gr/compute/v2.0/',
'astakos_url': 'https://accounts.okeanos.grnet.gr/identity/v2.0/',
'network_url': 'https://cyclades.okeanos.grnet.gr/network/v2.0'
}
VOMS_CONFIG = {
'enable_voms' : 'True',
'voms_policy' : '/etc/snf/voms.json',
'vomsdir_path' : '/etc/grid-security/vomsdir/',
'ca_path': '/etc/grid-security/certificates/',
'cert_dir' : '/etc/ssl/certs/',
'key_dir' : '/etc/ssl/private/',
'token' : '2iar6Uk36gBjFkdn0HfsxWZBB3TKQjoklYtZO9TRed0'
}
ASTAVOMS_URL = 'https://okeanos-astavoms.hellasgrid.gr'
'''
Created on Sep 9, 2013
@author: nassia
'''
from snfOCCI.config import SERVER_CONFIG
from occi.backend import ActionBackend, KindBackend, MixinBackend
from occi.exceptions import HTTPError
#Network Backend for snf-occi-server
class NetworkBackend(KindBackend, ActionBackend):
def create(self, entity, extras):
"""
Currently unsupported.
"""
raise HTTPError(501,"Currently not supported.")
def action(self, entity, action, attributes, extras):
"""
Currently unsupported.
"""
raise HTTPError(501, "Currently not supported.")
class IpNetworkBackend(MixinBackend):
def create(self, entity, extras):
"""
Currently unsupported.
"""
raise HTTPError(501,"Currently not supported.")
class IpNetworkInterfaceBackend(MixinBackend):
pass
class NetworkInterfaceBackend(KindBackend):
# Updating and Replacing compute instances not supported by Cyclades
def create(self, entity, extras):
"""
Currently unsupported.
"""
raise HTTPError(501,"Currently not supported.")
def action(self, entity, action, attributes, extras):
"""
Currently unsupported.
"""
raise HTTPError(501, "Currently not supported.")
def update(self, old, new, extras):
raise HTTPError(501, "Update is currently no applicable")
def replace(self, old, new, extras):
raise HTTPError(501, "Replace is currently no applicable")
from kamaki.clients.compute import ComputeClient
from kamaki.clients.cyclades import CycladesClient
from kamaki.config import Config
from kamaki.cli.config import Config
from occi import registry
from occi.core_model import Mixin
......@@ -15,3 +15,6 @@ class snfRegistry(registry.NonePersistentRegistry):
resource.identifier = key
super(snfRegistry, self).add_resource(key, resource, extras)
def set_hostname(self, hostname):
super(snfRegistry, self).set_hostname("https://okeanos-occi2.hellasgrid.gr:9000")
'''
Created on Jul 31, 2013
@author: nassia
'''
import json
import M2Crypto
import ast
from logging import getLogger, DEBUG
import voms_helper
from kamaki.clients import ClientError
from astavomaki.client import AstavomsClient
# import saferun
LOG = getLogger(__name__)
# Environment variable used to pass the request context
CONTEXT_ENV = 'snf.context'
SSL_CLIENT_S_DN_ENV = "SSL_CLIENT_S_DN"
SSL_CLIENT_CERT_ENV = "SSL_CLIENT_CERT"
SSL_CLIENT_CERT_CHAIN_ENV_PREFIX = "SSL_CLIENT_CERT_CHAIN_"
"""Global variables that contain VOMS related paths
"""
VOMS_POLICY = "/etc/snf/voms.json"
VOMSDIR_PATH = "/etc/grid-security/vomsdir/"
CA_PATH = "/etc/grid-security/certificates/"
VOMSAPI_LIB = "/usr/lib/libvomsapi.so.1"
#VOMSAPI_LIB = "voms.x86_64 0:2.0.10-3.el6"
AUTOCREATE_USERS = False
PARAMS_ENV = 'snf_voms.params'
ASTAVOMS_URL = 'https://okeanos-astavoms.hellasgrid.gr'
ASTAVOMS_TOKEN = 'Nothing for now'
#saferun.logger.setLevel(DEBUG)
#@saferun.saferun
#def test_and_log_astavoms(ssl_certs):
# astavoms = AstavomsClient(ASTAVOMS_URL, ASTAVOMS_TOKEN)
# saferun.logger.info('LALA 1')
# astavoms.astacert = '/etc/grid-security/certificates/TERENA-eScience-SSL-CA-3.pem'
# saferun.logger.info('LALA 2')
# saferun.logger.info('Authenticate this: {certs}'.format(certs=ssl_certs))
# saferun.logger.info('LALA 3')
# # Not tryied that yet, do it next
# from kamaki.clients.utils import https
# #https.patch_with_certs(CA_PATH)
# https.patch_ignore_ssl()
# r = astavoms.authenticate(**ssl_certs)
# from kamaki.clients.utils import https
# saferun.logger.info('SAFERUN RESPONSE: {0}'.format(json.dumps(r, indent=2)))
# return r
#@saferun.saferun
#def compare_results(old, new):
# saferun.logger.info('OLD: {old}\nNEW:{new}'.format(old=old, new=new))
# keydiff = set(old).symmetric_difference(new)
# saferun.logger.info('KEY DIFF: %s', keydiff)
# for k in set(old).intersection(new):
# ov, nv = old[k], new[k]
# msg = ' OK' if ov == nv else ' diff: %s != %s' % (ov, nv)
# saferun.logger.info('Check key %s ... %s' % (k, msg))
class VomsError():
"""Voms credential management error"""
errors = {
0: ('none', None),
1: ('nosocket', 'Socket problem'),
2: ('noident', 'Cannot identify itself (certificate problem)'),
3: ('comm', 'Server problem'),
4: ('param', 'Wrong parameters'),
5: ('noext', 'VOMS extension missing'),
6: ('noinit', 'Initialization error'),
7: ('time', 'Error in time checking'),
8: ('idcheck', 'User data in extension different from the real'),
9: ('extrainfo', 'VO name and URI missing'),
10: ('format', 'Wrong data format'),
11: ('nodata', 'Empty extension'),
12: ('parse', 'Parse error'),
13: ('dir', 'Directory error'),
14: ('sign', 'Signature error'),
15: ('server', 'Unidentifiable VOMS server'),
16: ('mem', 'Memory problems'),
17: ('verify', 'Generic verification error'),
18: ('type', 'Returned data of unknown type'),
19: ('order', 'Ordering different than required'),
20: ('servercode', 'Error from the server'),
21: ('notavail', 'Method not available'),
}
http_codes = {
5: (400, "Bad Request"),
11: (400, "Bad Request"),
14: (401, "Not Authorized"),
}
def __init__(self, code):
short, message = self.errors.get(code, ('oops',
'Unknown error %d' % code))
message = "(%s, %s)" % (code, message)
# super(VomsError, self).__init__(message=message)
code, title = self.http_codes.get(code, (500, "Unexpected Error"))
self.code = code
self.title = title
class VomsAuthN():
"""Filter that checks for the SSL data in the reqest.
Sets 'ssl' in the context as a dictionary containing this data.
"""
def __init__(self, *args, **kwargs):
self.astavoms = AstavomsClient(ASTAVOMS_URL, ASTAVOMS_TOKEN)
# VOMS stuff
try:
with open(VOMS_POLICY) as f:
self.voms_json = json.load(f)
except ValueError:
raise ClientError(
'Bad Formatted VOMS json',
details='The VOMS json data was not corectly formatted in file %s' % VOMS_POLICY)
except Exception as e:
raise ClientError(
'No loading of VOMS json file',
details='The VOMS json file located in %s was not loaded (%s:, e)' % (
VOMS_POLICY, type(e), e))
self._no_verify = False
#super(VomsAuthN, self).__init__(*args, **kwargs)
@staticmethod
def _get_cert_chain(ssl_info):
"""Return certificate and chain from the ssl info in M2Crypto format"""
cert = M2Crypto.X509.load_cert_string(ssl_info.get("cert"))
chain = M2Crypto.X509.X509_Stack()
for c in ssl_info.get("chain", []):
aux = M2Crypto.X509.load_cert_string(c)
chain.push(aux)
return cert, chain
def _get_voms_info(self, ssl_info):
"""Extract voms info from ssl_info and return dict with it."""
# Use astavoms instead of doing it by yourself
# astaresults = test_and_log_astavoms(ssl_info)