-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Intergrated ESI, allowing users to allocate ESI Network quotas
- Loading branch information
Showing
15 changed files
with
292 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# (Quan Pham) TODO Know we want to install in a test Devstack cluster | ||
# to test ESI |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Creates the appropriate credentials and runs tests | ||
# | ||
# Tests expect the resource to be name ESI | ||
set -xe | ||
|
||
source /opt/stack/devstack/openrc admin admin | ||
|
||
credential_name=$(openssl rand -base64 12) | ||
|
||
export ESI_ESI_APPLICATION_CREDENTIAL_SECRET=$( | ||
openstack application credential create "$credential_name" -f value -c secret) | ||
export ESI_ESI_APPLICATION_CREDENTIAL_ID=$( | ||
openstack application credential show "$credential_name" -f value -c id) | ||
|
||
export ESI_PUBLIC_NETWORK_ID=$(openstack network show public -f value -c id) | ||
|
||
if [[ ! "${CI}" == "true" ]]; then | ||
source /tmp/coldfront_venv/bin/activate | ||
fi | ||
|
||
export DJANGO_SETTINGS_MODULE="local_settings" | ||
export FUNCTIONAL_TESTS="True" | ||
export OS_AUTH_URL="http://$HOST_IP/identity" | ||
export KEYCLOAK_URL="http://$HOST_IP:8080" | ||
export KEYCLOAK_USER="admin" | ||
export KEYCLOAK_PASS="nomoresecret" | ||
export KEYCLOAK_REALM="master" | ||
|
||
coverage run --source="." -m django test coldfront_plugin_cloud.tests.functional.esi | ||
coverage report | ||
|
||
openstack application credential delete $ESI_ESI_APPLICATION_CREDENTIAL_ID |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import logging | ||
import functools | ||
import os | ||
|
||
from keystoneauth1.identity import v3 | ||
from keystoneauth1 import session | ||
|
||
|
||
from coldfront_plugin_cloud import attributes, utils | ||
|
||
from coldfront_plugin_cloud.openstack import OpenStackResourceAllocator | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
QUOTA_KEY_MAPPING = { | ||
'network': { | ||
'keys': { | ||
attributes.ESI_FLOATING_IPS: 'floatingip', | ||
attributes.ESI_NETWORKS: 'network' | ||
} | ||
}, | ||
} | ||
|
||
def get_session_for_resource(resource): | ||
auth_url = resource.get_attribute(attributes.RESOURCE_AUTH_URL) | ||
var_name = utils.env_safe_name(resource.name) | ||
auth = v3.ApplicationCredential( | ||
auth_url=auth_url, | ||
application_credential_id=os.environ.get( | ||
f'ESI_{var_name}_APPLICATION_CREDENTIAL_ID'), | ||
application_credential_secret=os.environ.get( | ||
f'ESI_{var_name}_APPLICATION_CREDENTIAL_SECRET') | ||
) | ||
return session.Session( | ||
auth, | ||
verify=os.environ.get('FUNCTIONAL_TESTS', '') != 'True' | ||
) | ||
|
||
|
||
class ESIResourceAllocator(OpenStackResourceAllocator): | ||
|
||
resource_type = 'esi' | ||
|
||
@functools.cached_property | ||
def session(self) -> session.Session: | ||
return get_session_for_resource(self.resource) | ||
|
||
def set_quota(self, project_id): | ||
for service_name, service in QUOTA_KEY_MAPPING.items(): | ||
if service_name not in self.available_service_types: | ||
logger.error(f"Service {service_name} needed for ESI allocation not available!") | ||
else: | ||
payload = dict() | ||
for coldfront_attr, openstack_key in service['keys'].items(): | ||
value = self.allocation.get_attribute(coldfront_attr) | ||
if value is not None: | ||
payload[openstack_key] = value | ||
|
||
if not payload: | ||
# Skip if service doesn't have any associated attributes | ||
continue | ||
|
||
if service_name == 'network': | ||
self.network.update_quota(project_id, body={'quota': payload}) | ||
|
||
|
||
def get_quota(self, project_id): | ||
quotas = dict() | ||
|
||
network_quota = self.network.show_quota(project_id)['quota'] | ||
for k in QUOTA_KEY_MAPPING['network']['keys'].values(): | ||
quotas[k] = network_quota.get(k) |
100 changes: 100 additions & 0 deletions
100
src/coldfront_plugin_cloud/management/commands/add_esi_resource.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
from django.core.management.base import BaseCommand | ||
from django.core.management import call_command | ||
|
||
from coldfront.core.resource.models import (Resource, | ||
ResourceAttribute, | ||
ResourceAttributeType, | ||
ResourceType) | ||
|
||
from coldfront_plugin_cloud import attributes | ||
|
||
|
||
class Command(BaseCommand): | ||
help = 'Create ESI resource' | ||
# (Quan Pham) TODO Know what other attributes an ESI resource must/could have | ||
# That could be set from cli, or through other means | ||
|
||
def add_arguments(self, parser): | ||
parser.add_argument('--name', type=str, required=True, | ||
help='Name of ESI resource') | ||
parser.add_argument('--auth-url', type=str, required=True, | ||
help='URL of the ESI esi Identity Endpoint') | ||
parser.add_argument('--users-domain', type=str, default='default', | ||
help='Domain ID to create users') | ||
parser.add_argument('--projects-domain', type=str, default='default', | ||
help='Domain ID to create projects') | ||
parser.add_argument('--idp', type=str, required=True, | ||
help='Identity provider configured in ESI cluster') | ||
parser.add_argument('--protocol', type=str, default='openid', | ||
help='Federation protocol (default: openid)') | ||
parser.add_argument('--role', type=str, default='member', | ||
help='Role for user when added to project (default: member)') | ||
parser.add_argument('--public-network', type=str, default='', | ||
help='Public network ID for default networks. ' | ||
'If not specified, no default network is ' | ||
'created for new projects.') | ||
parser.add_argument('--network-cidr', type=str, default='192.168.0.0/24', | ||
help='CIDR for default networks. ' | ||
'Ignored if no --public-network.') | ||
|
||
def handle(self, *args, **options): | ||
esi, _ = Resource.objects.get_or_create( | ||
resource_type=ResourceType.objects.get(name='ESI'), | ||
parent_resource=None, | ||
name=options['name'], | ||
description='ESI Bare Metal environment', | ||
is_available=True, | ||
is_public=True, | ||
is_allocatable=True | ||
) | ||
|
||
ResourceAttribute.objects.get_or_create( | ||
resource_attribute_type=ResourceAttributeType.objects.get( | ||
name=attributes.RESOURCE_AUTH_URL), | ||
resource=esi, | ||
value=options['auth_url'] | ||
) | ||
ResourceAttribute.objects.get_or_create( | ||
resource_attribute_type=ResourceAttributeType.objects.get( | ||
name=attributes.RESOURCE_PROJECT_DOMAIN), | ||
resource=esi, | ||
value=options['projects_domain'] | ||
) | ||
ResourceAttribute.objects.get_or_create( | ||
resource_attribute_type=ResourceAttributeType.objects.get( | ||
name=attributes.RESOURCE_USER_DOMAIN), | ||
resource=esi, | ||
value=options['users_domain'] | ||
) | ||
ResourceAttribute.objects.get_or_create( | ||
resource_attribute_type=ResourceAttributeType.objects.get( | ||
name=attributes.RESOURCE_IDP), | ||
resource=esi, | ||
value=options['idp'] | ||
) | ||
ResourceAttribute.objects.get_or_create( | ||
resource_attribute_type=ResourceAttributeType.objects.get( | ||
name=attributes.RESOURCE_FEDERATION_PROTOCOL), | ||
resource=esi, | ||
value=options['protocol'] | ||
) | ||
ResourceAttribute.objects.get_or_create( | ||
resource_attribute_type=ResourceAttributeType.objects.get( | ||
name=attributes.RESOURCE_ROLE), | ||
resource=esi, | ||
value=options['role'] | ||
) | ||
|
||
if options['public_network']: | ||
ResourceAttribute.objects.get_or_create( | ||
resource_attribute_type=ResourceAttributeType.objects.get( | ||
name=attributes.RESOURCE_DEFAULT_PUBLIC_NETWORK), | ||
resource=esi, | ||
value=options['public_network'] | ||
) | ||
ResourceAttribute.objects.get_or_create( | ||
resource_attribute_type=ResourceAttributeType.objects.get( | ||
name=attributes.RESOURCE_DEFAULT_NETWORK_CIDR), | ||
resource=esi, | ||
value=options['network_cidr'] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/coldfront_plugin_cloud/tests/functional/esi/test_allocations.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import os | ||
import unittest | ||
import uuid | ||
|
||
from coldfront_plugin_cloud import esi | ||
from coldfront_plugin_cloud.tests import base | ||
|
||
from django.core.management import call_command | ||
from keystoneclient.v3 import client | ||
from cinderclient import client as cinderclient | ||
from neutronclient.v2_0 import client as neutronclient | ||
from novaclient import client as novaclient | ||
|
||
@unittest.skipUnless(os.getenv('FUNCTIONAL_TESTS'), 'Functional tests not enabled.') | ||
class TestAllocation(base.TestBase): | ||
|
||
# (Quan Pham) TODO What are the test cases for an ESI resource? | ||
# What do we need to test? | ||
|
||
def setUp(self) -> None: | ||
super().setUp() | ||
self.resource = self.new_esi_resource(name='ESI', | ||
auth_url=os.getenv('OS_AUTH_URL')) | ||
self.session = esi.get_session_for_resource(self.resource) | ||
self.identity = client.Client(session=self.session) | ||
self.compute = novaclient.Client(session=self.session, version=2) | ||
self.volume = cinderclient.Client(session=self.session, version=3) | ||
self.networking = neutronclient.Client(session=self.session) | ||
self.role_member = self.identity.roles.find(name='member') | ||
|
||
def test_new_ESI_allocation(self): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.